fix(statistics): scope album and song totals to selected music library

getGenres() is not musicFolder-scoped, so summing genre counts showed
global totals while other statistics respected the library filter.
Derive album and track counts from the same paginated getAlbumList
pass used for playtime, with the same 5000-album cap and ≥ prefix when
capped.
This commit is contained in:
Maxim Isaev
2026-04-09 00:59:16 +03:00
parent d16a99a6f9
commit c569ff5f34
+22 -8
View File
@@ -70,19 +70,24 @@ export default function Statistics() {
setFrequent(fr); setFrequent(fr);
setHighest(hi); setHighest(hi);
setArtistCount(a.length); setArtistCount(a.length);
setTotalSongs(g.reduce((acc: number, genre: SubsonicGenre) => acc + genre.songCount, 0)); // Album/song totals come from paginated getAlbumList (see playtime effect) — getGenres is not musicFolder-scoped.
setTotalAlbums(g.reduce((acc: number, genre: SubsonicGenre) => acc + genre.albumCount, 0));
const sorted = [...g].sort((a, b) => b.songCount - a.songCount); const sorted = [...g].sort((a, b) => b.songCount - a.songCount);
setGenres(sorted); setGenres(sorted);
setLoading(false); setLoading(false);
}).catch(() => setLoading(false)); }).catch(() => setLoading(false));
}, [musicLibraryFilterVersion]); }, [musicLibraryFilterVersion]);
// Background fetch: total playtime (paginate getAlbumList up to 10 pages of 500) // Background: playtime + album/song counts (same paginated list as library filter; caps at 5000 albums)
useEffect(() => { useEffect(() => {
let cancelled = false; let cancelled = false;
setTotalPlaytime(null);
setTotalAlbums(null);
setTotalSongs(null);
setPlaytimeCapped(false);
(async () => { (async () => {
let total = 0; let playtimeSec = 0;
let albumsCounted = 0;
let songsCounted = 0;
let offset = 0; let offset = 0;
const pageSize = 500; const pageSize = 500;
const maxPages = 10; const maxPages = 10;
@@ -91,7 +96,11 @@ export default function Statistics() {
try { try {
const albums = await getAlbumList('newest', pageSize, offset); const albums = await getAlbumList('newest', pageSize, offset);
if (cancelled) return; if (cancelled) return;
for (const a of albums) total += (a.duration ?? 0); for (const a of albums) {
playtimeSec += a.duration ?? 0;
albumsCounted += 1;
songsCounted += a.songCount ?? 0;
}
if (albums.length < pageSize) break; if (albums.length < pageSize) break;
if (page === maxPages - 1) capped = true; if (page === maxPages - 1) capped = true;
offset += pageSize; offset += pageSize;
@@ -100,7 +109,9 @@ export default function Statistics() {
} }
} }
if (!cancelled) { if (!cancelled) {
setTotalPlaytime(total); setTotalPlaytime(playtimeSec);
setTotalAlbums(albumsCounted);
setTotalSongs(songsCounted);
setPlaytimeCapped(capped); setPlaytimeCapped(capped);
} }
})(); })();
@@ -167,10 +178,13 @@ export default function Statistics() {
? t('statistics.computing') ? t('statistics.computing')
: (playtimeCapped ? '≥ ' : '') + formatPlaytime(totalPlaytime); : (playtimeCapped ? '≥ ' : '') + formatPlaytime(totalPlaytime);
const countDisplay = (n: number | null) =>
n === null ? t('statistics.computing') : (playtimeCapped ? '≥ ' : '') + n.toLocaleString();
const stats = [ const stats = [
{ label: t('statistics.statArtists'), value: artistCount?.toLocaleString() ?? '—' }, { label: t('statistics.statArtists'), value: artistCount?.toLocaleString() ?? '—' },
{ label: t('statistics.statAlbums'), value: totalAlbums?.toLocaleString() ?? '—' }, { label: t('statistics.statAlbums'), value: countDisplay(totalAlbums) },
{ label: t('statistics.statSongs'), value: totalSongs?.toLocaleString() ?? '—' }, { label: t('statistics.statSongs'), value: countDisplay(totalSongs) },
{ label: t('statistics.statPlaytime'), value: playtimeDisplay }, { label: t('statistics.statPlaytime'), value: playtimeDisplay },
]; ];