feat: local-first data pipeline — check library before API calls
Audit of all external calls across explore components, with local sources checked first: 1. loadThumbnails: seeds thumbnailCache from library album cover art (CoverArtMedium/Small by MBID) before building the API request list. Library albums show cover art instantly; only non-library albums hit the GetThumbnails API. 2. loadArtistImages: seeds artistImageCache from library store (ImageMedium/Small by MBID) before the sequential API loop. Library artists show images instantly; only non-library artists hit GetArtistImageURL. 3. checkLibrary (explore-view): checks library store MBIDs frontend-side for artists and albums. Only falls back to CheckLibraryMBIDs API for recordings (not in library store). 4. checkLibrary (artist-details): same frontend-first approach using library album MBIDs. 5. hydrateFromCache (artist-details): now also checks library store directly for artist images when explore cache is empty (handles direct navigation without prior search).
This commit is contained in:
@@ -773,15 +773,26 @@ export class ExploreArtistDetails extends LitElement {
|
|||||||
* Shows cached data instantly before API calls complete.
|
* Shows cached data instantly before API calls complete.
|
||||||
*/
|
*/
|
||||||
private hydrateFromCache(mbid: string) {
|
private hydrateFromCache(mbid: string) {
|
||||||
// Artist image from explore cache (populated by search results).
|
// Artist image: check explore cache first, then library store.
|
||||||
const cachedArtist = exploreCache.getArtist(mbid);
|
if (!this.artistImageURL) {
|
||||||
if (cachedArtist) {
|
const cachedArtist = exploreCache.getArtist(mbid);
|
||||||
if (cachedArtist.imageURL) {
|
if (cachedArtist) {
|
||||||
this.artistImageURL = cachedArtist.imageURL;
|
this.artistImageURL = cachedArtist.imageURL
|
||||||
} else if (cachedArtist.imageMedium) {
|
|| cachedArtist.imageMedium
|
||||||
this.artistImageURL = cachedArtist.imageMedium;
|
|| cachedArtist.imageSmall
|
||||||
} else if (cachedArtist.imageSmall) {
|
|| '';
|
||||||
this.artistImageURL = cachedArtist.imageSmall;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.artistImageURL) {
|
||||||
|
const cachedArtists = libraryStore.cachedArtists;
|
||||||
|
if (cachedArtists) {
|
||||||
|
for (const a of cachedArtists) {
|
||||||
|
if (a.MBID === mbid) {
|
||||||
|
this.artistImageURL = a.ImageMedium || a.ImageSmall || '';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -930,6 +941,30 @@ export class ExploreArtistDetails extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async checkLibrary() {
|
private async checkLibrary() {
|
||||||
|
// Check frontend-side first.
|
||||||
|
const cachedAlbums = libraryStore.cachedAlbums;
|
||||||
|
if (cachedAlbums) {
|
||||||
|
const localMBIDs = new Set<string>();
|
||||||
|
for (const a of cachedAlbums) {
|
||||||
|
if (a.MBID) localMBIDs.add(a.MBID);
|
||||||
|
}
|
||||||
|
|
||||||
|
let updated = false;
|
||||||
|
|
||||||
|
for (const rg of this.releaseGroups) {
|
||||||
|
if (rg.mbid && localMBIDs.has(rg.mbid)) {
|
||||||
|
this.libraryMBIDs.add(rg.mbid);
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updated) {
|
||||||
|
this.requestUpdate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to backend.
|
||||||
const mbids: string[] = [];
|
const mbids: string[] = [];
|
||||||
|
|
||||||
for (const rg of this.releaseGroups) {
|
for (const rg of this.releaseGroups) {
|
||||||
|
|||||||
@@ -858,7 +858,27 @@ export class ExploreView extends LitElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect MBIDs that need fetching.
|
// Seed thumbnails from library album cover art (instant, no API).
|
||||||
|
const cachedAlbums = libraryStore.cachedAlbums;
|
||||||
|
if (cachedAlbums) {
|
||||||
|
const libAlbumsByMBID = new Map<string, string>();
|
||||||
|
for (const a of cachedAlbums) {
|
||||||
|
if (a.MBID && (a.CoverArtMedium || a.CoverArtSmall)) {
|
||||||
|
libAlbumsByMBID.set(a.MBID, a.CoverArtMedium || a.CoverArtSmall);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const rg of this.results.releaseGroups) {
|
||||||
|
if (!this.thumbnailCache.has(rg.mbid)) {
|
||||||
|
const localArt = libAlbumsByMBID.get(rg.mbid) || (rg as any)._coverArt;
|
||||||
|
if (localArt) {
|
||||||
|
this.thumbnailCache.set(rg.mbid, localArt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect MBIDs that still need fetching from the API.
|
||||||
const requests: ThumbnailRequest[] = [];
|
const requests: ThumbnailRequest[] = [];
|
||||||
|
|
||||||
for (const rg of this.results.releaseGroups) {
|
for (const rg of this.results.releaseGroups) {
|
||||||
@@ -917,7 +937,29 @@ export class ExploreView extends LitElement {
|
|||||||
private async loadArtistImages() {
|
private async loadArtistImages() {
|
||||||
if (!this.results?.artists?.length) return;
|
if (!this.results?.artists?.length) return;
|
||||||
|
|
||||||
// Load sequentially to avoid hammering the MB rate limiter.
|
// Seed from library store first (instant, no API).
|
||||||
|
const cachedArtists = libraryStore.cachedArtists;
|
||||||
|
if (cachedArtists) {
|
||||||
|
const libByMBID = new Map<string, string>();
|
||||||
|
for (const a of cachedArtists) {
|
||||||
|
if (a.MBID && (a.ImageMedium || a.ImageSmall)) {
|
||||||
|
libByMBID.set(a.MBID, a.ImageMedium || a.ImageSmall);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const a of this.results.artists) {
|
||||||
|
if (!this.artistImageCache.has(a.mbid) && a.mbid) {
|
||||||
|
const local = libByMBID.get(a.mbid) || (a as any)._imageMedium || (a as any)._imageSmall;
|
||||||
|
if (local) {
|
||||||
|
this.artistImageCache.set(a.mbid, local);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.requestUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch remaining from API (only artists not yet resolved).
|
||||||
for (const a of this.results.artists) {
|
for (const a of this.results.artists) {
|
||||||
if (this.artistImageCache.has(a.mbid)) continue;
|
if (this.artistImageCache.has(a.mbid)) continue;
|
||||||
|
|
||||||
@@ -942,14 +984,50 @@ export class ExploreView extends LitElement {
|
|||||||
private async checkLibrary() {
|
private async checkLibrary() {
|
||||||
if (!this.results) return;
|
if (!this.results) return;
|
||||||
|
|
||||||
const mbids: string[] = [];
|
// Check frontend-side first using library store MBIDs.
|
||||||
|
const cachedArtists = libraryStore.cachedArtists;
|
||||||
|
const cachedAlbums = libraryStore.cachedAlbums;
|
||||||
|
const localMBIDs = new Set<string>();
|
||||||
|
|
||||||
|
if (cachedArtists) {
|
||||||
|
for (const a of cachedArtists) {
|
||||||
|
if (a.MBID) localMBIDs.add(a.MBID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cachedAlbums) {
|
||||||
|
for (const a of cachedAlbums) {
|
||||||
|
if (a.MBID) localMBIDs.add(a.MBID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let updated = false;
|
||||||
|
|
||||||
for (const a of this.results.artists ?? []) {
|
for (const a of this.results.artists ?? []) {
|
||||||
if (a.mbid) mbids.push(a.mbid);
|
if (a.mbid && localMBIDs.has(a.mbid)) {
|
||||||
|
this.libraryMBIDs.add(a.mbid);
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const rg of this.results.releaseGroups ?? []) {
|
for (const rg of this.results.releaseGroups ?? []) {
|
||||||
if (rg.mbid) mbids.push(rg.mbid);
|
if (rg.mbid && localMBIDs.has(rg.mbid)) {
|
||||||
|
this.libraryMBIDs.add(rg.mbid);
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updated) {
|
||||||
|
this.requestUpdate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to backend check for recordings and edge cases
|
||||||
|
// (recordings aren't in the library store cache).
|
||||||
|
const mbids: string[] = [];
|
||||||
|
|
||||||
|
for (const r of this.results.recordings ?? []) {
|
||||||
|
if (r.mbid) mbids.push(r.mbid);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mbids.length === 0) return;
|
if (mbids.length === 0) return;
|
||||||
|
|||||||
Reference in New Issue
Block a user