#911: redownload via the album's CANONICAL source (covers the 67% with multiple ids)
The first pass only checked spotify then itunes. But ~67% of a real library (46k/69k albums) carry BOTH a spotify and itunes id, and the canonical priority is spotify>deezer>itunes>mb>…, so a spotify-first guess diverges from what the Enhanced view actually tags/displays the album as (e.g. a deezer-canonical album with an itunes id too). Now redownload reuses _getEnhancedAlbumCanonicalSource (the view's single source of truth) and fetches via the same /api/album/<id>/tracks?source= endpoint the view uses for its canonical tracklist — so a redownload is always the exact edition on screen, across every source. The stored spotify/iTunes id + a last-resort search remain as fallbacks. Frontend-only; the album endpoints and canonical resolver are unchanged.
This commit is contained in:
parent
69cb51cc13
commit
65f73fae92
1 changed files with 53 additions and 32 deletions
|
|
@ -5417,14 +5417,20 @@ async function redownloadLibraryAlbum(album, artistName, btn) {
|
|||
const albumName = album.title || '';
|
||||
const spotifyAlbumId = album.spotify_album_id || '';
|
||||
const itunesAlbumId = album.itunes_album_id || '';
|
||||
// #911 — the album's CANONICAL source (the same one the Enhanced view tags + displays it as)
|
||||
// wins. Redownload must pull THAT exact edition, not a fresh search that can resolve to a
|
||||
// different one (issue: matched the 66-track 'Original Soundtrack Collection', a search got
|
||||
// the 19-track 'Volume 1'). _getEnhancedAlbumCanonicalSource is the single source of truth
|
||||
// for which source identifies this album, across spotify/deezer/itunes/musicbrainz/…
|
||||
const canonical = _getEnhancedAlbumCanonicalSource(album);
|
||||
|
||||
if (!spotifyAlbumId && !itunesAlbumId && !albumName) {
|
||||
if (!canonical && !spotifyAlbumId && !itunesAlbumId && !albumName) {
|
||||
showToast('No album ID or name available for redownload', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch a specific album edition by its source id. The iTunes endpoint returns a
|
||||
// Spotify-shaped payload, so all the downstream handling is identical.
|
||||
// Fetch a specific album edition by its source id (the Spotify/iTunes endpoints both return
|
||||
// a Spotify-shaped payload, so downstream handling is identical).
|
||||
const fetchAlbumBySource = (source, id, name, artist) => {
|
||||
const params = new URLSearchParams({ name: name || albumName, artist: artist || artistName || '' });
|
||||
const base = source === 'itunes' ? '/api/itunes/album/' : '/api/spotify/album/';
|
||||
|
|
@ -5435,40 +5441,55 @@ async function redownloadLibraryAlbum(album, artistName, btn) {
|
|||
try {
|
||||
if (btn) { btn.disabled = true; btn.textContent = 'Loading...'; }
|
||||
|
||||
// #911 — redownload the edition the user ACTUALLY matched, using the album row's
|
||||
// stored source id. A fresh search can resolve to a different edition (e.g. a
|
||||
// single-volume release instead of the matched full collection), so use the
|
||||
// spotify/iTunes id when present and only fall back to a search if neither exists.
|
||||
let response;
|
||||
if (spotifyAlbumId) {
|
||||
response = await fetchAlbumBySource('spotify', spotifyAlbumId);
|
||||
} else if (itunesAlbumId) {
|
||||
response = await fetchAlbumBySource('itunes', itunesAlbumId);
|
||||
}
|
||||
let albumData = null;
|
||||
|
||||
if (!response || !response.ok) {
|
||||
const query = `${artistName || ''} ${albumName}`.trim();
|
||||
const searchResp = await fetch('/api/enhanced-search', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ query })
|
||||
});
|
||||
if (!searchResp.ok) throw new Error('Album search failed');
|
||||
const searchData = await searchResp.json();
|
||||
const spotHit = searchData.spotify_albums?.[0];
|
||||
const found = spotHit || searchData.itunes_albums?.[0];
|
||||
if (!found || !found.id) {
|
||||
showToast(`Could not find "${albumName}" by ${artistName || 'unknown'}`, 'warning');
|
||||
return;
|
||||
// 1) Primary: the canonical tagged source (any source), via the SAME
|
||||
// /api/album/<id>/tracks endpoint the Enhanced view uses for its canonical tracklist —
|
||||
// so a redownload is always the album the user is actually looking at.
|
||||
if (canonical) {
|
||||
const params = new URLSearchParams({ name: albumName, artist: artistName || '', source: canonical.source });
|
||||
const r = await fetch(`/api/album/${encodeURIComponent(canonical.id)}/tracks?${params}`);
|
||||
if (r.ok) {
|
||||
const data = await r.json();
|
||||
if (data && data.success && Array.isArray(data.tracks) && data.tracks.length) {
|
||||
albumData = { ...data.album, tracks: data.tracks }; // normalize to {…, tracks:[]}
|
||||
}
|
||||
}
|
||||
// Fetch from the MATCHING source endpoint — the old fallback always hit the
|
||||
// Spotify endpoint, which is wrong for an iTunes search hit.
|
||||
response = await fetchAlbumBySource(spotHit ? 'spotify' : 'itunes', found.id, found.name, found.artist);
|
||||
}
|
||||
|
||||
if (!response.ok) throw new Error(`Failed to load album: ${response.status}`);
|
||||
// 2) Fallback: the stored spotify/iTunes id, then a last-resort search.
|
||||
if (!albumData) {
|
||||
let response;
|
||||
if (spotifyAlbumId) {
|
||||
response = await fetchAlbumBySource('spotify', spotifyAlbumId);
|
||||
} else if (itunesAlbumId) {
|
||||
response = await fetchAlbumBySource('itunes', itunesAlbumId);
|
||||
}
|
||||
|
||||
if (!response || !response.ok) {
|
||||
const query = `${artistName || ''} ${albumName}`.trim();
|
||||
const searchResp = await fetch('/api/enhanced-search', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ query })
|
||||
});
|
||||
if (!searchResp.ok) throw new Error('Album search failed');
|
||||
const searchData = await searchResp.json();
|
||||
const spotHit = searchData.spotify_albums?.[0];
|
||||
const found = spotHit || searchData.itunes_albums?.[0];
|
||||
if (!found || !found.id) {
|
||||
showToast(`Could not find "${albumName}" by ${artistName || 'unknown'}`, 'warning');
|
||||
return;
|
||||
}
|
||||
// Fetch from the MATCHING source endpoint — the old fallback always hit Spotify,
|
||||
// which is wrong for an iTunes search hit.
|
||||
response = await fetchAlbumBySource(spotHit ? 'spotify' : 'itunes', found.id, found.name, found.artist);
|
||||
}
|
||||
|
||||
if (!response.ok) throw new Error(`Failed to load album: ${response.status}`);
|
||||
albumData = await response.json();
|
||||
}
|
||||
|
||||
const albumData = await response.json();
|
||||
if (!albumData || !albumData.tracks || albumData.tracks.length === 0) {
|
||||
showToast(`No tracks found for "${albumName}"`, 'warning');
|
||||
return;
|
||||
|
|
|
|||
Loading…
Reference in a new issue