diff --git a/core/downloads/lifecycle.py b/core/downloads/lifecycle.py index bb3d7d4a..aa784d8f 100644 --- a/core/downloads/lifecycle.py +++ b/core/downloads/lifecycle.py @@ -57,7 +57,6 @@ class LifecycleDeps: submit_failed_to_wishlist_with_auto_completion: Callable[[str], None] # async — submits to executor process_failed_to_wishlist: Callable[[str], None] # sync — direct call (used by v2 path) process_failed_to_wishlist_with_auto_completion: Callable[[str], None] # sync — direct call (used by v2 path) - ensure_spotify_track_format: Callable get_track_artist_name: Callable check_and_remove_from_wishlist: Callable regenerate_batch_m3u: Callable @@ -65,6 +64,17 @@ class LifecycleDeps: tidal_discovery_states: dict deezer_discovery_states: dict spotify_public_discovery_states: dict + ensure_wishlist_track_format: Callable | None = None + ensure_spotify_track_format: Callable | None = None + + def __post_init__(self) -> None: + if self.ensure_wishlist_track_format is None: + self.ensure_wishlist_track_format = self.ensure_spotify_track_format + if self.ensure_spotify_track_format is None: + self.ensure_spotify_track_format = self.ensure_wishlist_track_format + + if self.ensure_wishlist_track_format is None: + raise ValueError("LifecycleDeps requires a wishlist track format helper") # --------------------------------------------------------------------------- @@ -188,8 +198,8 @@ def on_download_completed(batch_id: str, task_id: str, success: bool, deps: Life # Build track_info structure matching sync.py's permanently_failed_tracks format original_track_info = task.get('track_info', {}) - # Ensure spotify_track has proper structure for wishlist service - spotify_track_data = deps.ensure_spotify_track_format(original_track_info) + # Ensure wishlist track has proper structure for wishlist service + wishlist_track_data = deps.ensure_wishlist_track_format(original_track_info) track_info = { 'download_index': task.get('track_index', 0), @@ -197,7 +207,8 @@ def on_download_completed(batch_id: str, task_id: str, success: bool, deps: Life 'track_name': original_track_info.get('name', 'Unknown Track'), 'artist_name': deps.get_track_artist_name(original_track_info), 'retry_count': task.get('retry_count', 0), - 'spotify_track': spotify_track_data, # Properly formatted spotify track for wishlist + 'track_data': wishlist_track_data, + 'spotify_track': wishlist_track_data, # Backward-compatible alias for older callers 'failure_reason': 'Download cancelled' if task_status == 'cancelled' else ('No matching track found' if task_status == 'not_found' else 'Download failed'), 'candidates': task.get('cached_candidates', []), # Include search results if available } diff --git a/tests/downloads/test_downloads_lifecycle.py b/tests/downloads/test_downloads_lifecycle.py index 8f82cb72..fb67d157 100644 --- a/tests/downloads/test_downloads_lifecycle.py +++ b/tests/downloads/test_downloads_lifecycle.py @@ -100,7 +100,7 @@ def _build_deps( submit_failed_to_wishlist_with_auto_completion=submit_failed_auto or rec('submit_failed_auto'), process_failed_to_wishlist=process_failed or rec('process_failed'), process_failed_to_wishlist_with_auto_completion=process_failed_auto or rec('process_failed_auto'), - ensure_spotify_track_format=lambda track: track, + ensure_wishlist_track_format=lambda track: track, get_track_artist_name=lambda track: 'Artist', check_and_remove_from_wishlist=rec('check_wishlist'), regenerate_batch_m3u=rec('regen_m3u'), @@ -258,6 +258,8 @@ def test_on_complete_failed_task_appended_to_permanently_failed_tracks(): lc.on_download_completed('b1', 't1', False, deps) assert len(download_batches['b1']['permanently_failed_tracks']) == 1 assert download_batches['b1']['permanently_failed_tracks'][0]['track_name'] == 'Money' + assert download_batches['b1']['permanently_failed_tracks'][0]['track_data'] == {'name': 'Money'} + assert download_batches['b1']['permanently_failed_tracks'][0]['spotify_track'] == {'name': 'Money'} def test_on_complete_cancelled_task_added_to_cancelled_tracks(): diff --git a/web_server.py b/web_server.py index 51342fa7..1db01c3f 100644 --- a/web_server.py +++ b/web_server.py @@ -113,7 +113,7 @@ from core.imports.context import ( from core.wishlist.payloads import ( build_cancelled_task_wishlist_payload as _build_cancelled_task_wishlist_payload, build_failed_track_wishlist_context as _build_failed_track_wishlist_context, - ensure_spotify_track_format as _ensure_spotify_track_format, + ensure_wishlist_track_format as _ensure_wishlist_track_format, get_track_artist_name as _get_track_artist_name, ) from core.wishlist.routes import ( @@ -17912,7 +17912,7 @@ def _build_lifecycle_deps(): ), process_failed_to_wishlist=_process_failed_tracks_to_wishlist_exact, process_failed_to_wishlist_with_auto_completion=_process_failed_tracks_to_wishlist_exact_with_auto_completion, - ensure_spotify_track_format=_ensure_spotify_track_format, + ensure_wishlist_track_format=_ensure_wishlist_track_format, get_track_artist_name=_get_track_artist_name, check_and_remove_from_wishlist=_check_and_remove_from_wishlist, regenerate_batch_m3u=_regenerate_batch_m3u, @@ -18008,9 +18008,9 @@ def _process_failed_tracks_to_wishlist_exact(batch_id): # Skip wing-it fallback tracks — they had no real metadata match, # so adding them to wishlist would just retry with the same raw data. - # Check the track ID prefix since _ensure_spotify_track_format overwrites source. - sp_track = failed_track_info.get('spotify_track', {}) - sp_id = sp_track.get('id', '') if isinstance(sp_track, dict) else '' + # Check the track ID prefix since the wishlist payload helper overwrites source. + track_data = failed_track_info.get('track_data') or failed_track_info.get('spotify_track', {}) + sp_id = track_data.get('id', '') if isinstance(track_data, dict) else '' if str(sp_id).startswith('wing_it_'): wing_it_skipped += 1 logger.info(f"[Wishlist Processing] Skipping wing-it track: {track_name}")