From d4e80fdaa0413ea05f30dace8cc37f6e0b1fce07 Mon Sep 17 00:00:00 2001 From: BoulderBadgeDad Date: Tue, 23 Jun 2026 19:21:50 -0700 Subject: [PATCH] #915: redownload pulls full album_data from the primary source for iTunes/Deezer too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Second leak of the same class: redownload_start built full album_data (release_date/album_type/ total_tracks) only in the Spotify branch. The iTunes and Deezer branches set just track/disc number and left album_data lean ({'name': ...}), so single-track redownloads on those sources dropped the $year — same symptom as #915 in the add/download path. Fix: both branches now fetch the album via get_album_for_source (cached, source-aware) and build album_data through the shared _album_data_from_source helper, mirroring the Spotify branch. Falls back to the lean default if the fetch returns nothing (no regression). get_album is cached on both iTunes and Deezer, so no extra API cost. Tests: _album_data_from_source (full build, image-url fallback, defaults). 694 library+downloads tests green. --- core/library/redownload.py | 48 ++++++++++++++++++--- tests/library/test_redownload_album_data.py | 38 ++++++++++++++++ 2 files changed, 80 insertions(+), 6 deletions(-) create mode 100644 tests/library/test_redownload_album_data.py diff --git a/core/library/redownload.py b/core/library/redownload.py index 08d5cd70..a2bc895f 100644 --- a/core/library/redownload.py +++ b/core/library/redownload.py @@ -16,6 +16,7 @@ from core.runtime_state import ( download_tasks, tasks_lock, ) +from core.metadata.album_tracks import get_album_for_source from core.metadata.registry import ( get_deezer_client, get_itunes_client, @@ -26,6 +27,27 @@ from database.music_database import get_database logger = logging.getLogger(__name__) +def _album_data_from_source(full: dict, album_id: str, fallback_name: str) -> dict: + """Build the redownload `album_data` from a primary-source get_album result (#915). + + Mirrors the Spotify branch's album_data shape so iTunes/Deezer redownloads carry the + real release_date / album_type / total_tracks — instead of a lean {'name': ...} that + drops the $year and forces a manual reorganize afterwards.""" + images = full.get('images') or [] + image_url = full.get('image_url') or '' + if not image_url and images and isinstance(images[0], dict): + image_url = images[0].get('url', '') + return { + 'id': str(full.get('id') or album_id), + 'name': full.get('name') or fallback_name, + 'release_date': full.get('release_date', ''), + 'album_type': full.get('album_type', 'album'), + 'total_tracks': full.get('total_tracks', 0), + 'images': images, + 'image_url': image_url, + } + + def _get_itunes_client(): """Mirror of web_server._get_itunes_client — delegates to registry.""" return get_itunes_client() @@ -141,12 +163,26 @@ def redownload_start(track_id): 'images': album_images, 'image_url': album_images[0]['url'] if album_images else '', } - elif meta_source == 'itunes': - track_number = full_track_details.get('trackNumber') - disc_number = full_track_details.get('discNumber', 1) - elif meta_source == 'deezer': - track_number = full_track_details.get('track_position') - disc_number = full_track_details.get('disk_number', 1) + elif meta_source in ('itunes', 'deezer'): + # #915: parity with the Spotify branch + Reorganize — pull the full album from + # the primary source so album_data carries release_date/album_type/total_tracks + # (was lean {'name': ...}, which dropped the $year on iTunes/Deezer redownloads). + if meta_source == 'itunes': + track_number = full_track_details.get('trackNumber') + disc_number = full_track_details.get('discNumber', 1) + _alb_id = full_track_details.get('collectionId') + else: + track_number = full_track_details.get('track_position') + disc_number = full_track_details.get('disk_number', 1) + _alb_id = (full_track_details.get('album') or {}).get('id') + if _alb_id: + try: + _full_album = get_album_for_source(meta_source, str(_alb_id)) + except Exception as _alb_err: # noqa: BLE001 — never let metadata break redownload + logger.debug("[Redownload] %s album fetch failed: %s", meta_source, _alb_err) + _full_album = None + if isinstance(_full_album, dict): + album_data = _album_data_from_source(_full_album, str(_alb_id), metadata.get('album', '')) track_data = { 'id': meta_id, diff --git a/tests/library/test_redownload_album_data.py b/tests/library/test_redownload_album_data.py new file mode 100644 index 00000000..8a648951 --- /dev/null +++ b/tests/library/test_redownload_album_data.py @@ -0,0 +1,38 @@ +"""Redownload builds full album_data from the primary source (#915). + +iTunes/Deezer single-track redownloads used to carry a lean album_data ({'name': ...}), +dropping the $year folder. _album_data_from_source mirrors the Spotify branch so the real +release_date / album_type / total_tracks come through. +""" + +from __future__ import annotations + +from core.library.redownload import _album_data_from_source + + +def test_builds_full_album_data_from_source(): + full = {'id': 'it-1', 'name': 'Big OST', 'release_date': '2024-04-17', + 'album_type': 'album', 'total_tracks': 70, 'image_url': 'http://x/cover.jpg'} + out = _album_data_from_source(full, 'it-1', 'fallback') + assert out['release_date'] == '2024-04-17' # real date, not YYYY-01-01 + assert out['album_type'] == 'album' + assert out['total_tracks'] == 70 + assert out['id'] == 'it-1' + assert out['name'] == 'Big OST' + assert out['image_url'] == 'http://x/cover.jpg' + + +def test_image_url_falls_back_to_images_array(): + full = {'name': 'A', 'release_date': '2020-01-01', 'images': [{'url': 'http://x/img.jpg'}]} + out = _album_data_from_source(full, 'a1', 'fb') + assert out['image_url'] == 'http://x/img.jpg' + + +def test_defaults_when_fields_missing(): + out = _album_data_from_source({}, 'a1', 'Fallback Album') + assert out['id'] == 'a1' # falls back to the queried id + assert out['name'] == 'Fallback Album' + assert out['album_type'] == 'album' # default + assert out['total_tracks'] == 0 + assert out['release_date'] == '' + assert out['image_url'] == ''