Files
psysonic/src/utils/library/albumBrowseLoad.ts
T
cucadmuh ddf10ee01d feat(genres): local index genre browse with Subsonic fallback (#937)
* feat(genres): genre detail browse via local index with aligned counts

Move genre detail albums/play/shuffle onto the local library index with
Albums-style in-page scroll, session restore, and genre-scoped stash. Unify
genre album totals between the cloud and detail pages via
library_get_genre_album_counts, and fix grouped browse totals to count
distinct albums rather than matching tracks.

* perf(genres): local genre browse with scoped counts cache

Add dedicated Rust genre album pagination and indexes, slice-mode grid
loading, library-filter-aware counts, and a long-lived in-memory catalog
cache invalidated on sync so genre pages avoid repeated full-library SQL.

* fix(genres): restore scroll after album back; play hold-to-shuffle

Pin restore display count in refs so clearing the return stash no longer
reloads the genre grid mid-restore. Load the first SQL page only (60 rows),
use long-press on Play for shuffle, and add genre play tooltips.

* fix(genres): fall back to Subsonic byGenre when local index unavailable

Genre detail album grid now matches All Albums: try library_list_albums_by_genre
first, then getAlbumsByGenre when the index is off, not ready, or errors.

* docs: add CHANGELOG and credits for PR #937
2026-06-01 04:20:18 +03:00

91 lines
2.8 KiB
TypeScript

/**
* Albums browse: local index + Subsonic network paths.
* Filters and types live in sibling modules; this file is the fetch entry point.
*/
export type { AlbumCompFilter } from './albumCompilation';
export type {
AlbumBrowseFetchCallbacks,
AlbumBrowsePageResult,
AlbumBrowseQuery,
GenreFilterOption,
} from './albumBrowseTypes';
export {
albumBrowseHasGenreFilter,
albumBrowseHasServerFilters,
filterAlbumsByCompilation,
filterAlbumsByStarred,
} from './albumBrowseFilters';
export { runLocalAlbumBrowse } from './albumBrowseLocal';
import { countGenresFromAlbums, filterAlbumsByCompilation } from './albumBrowseFilters';
import { runLocalAlbumBrowse } from './albumBrowseLocal';
import { fetchAlbumBrowseNetwork } from './albumBrowseNetwork';
import { fetchStarredAlbumBrowse } from './albumBrowseStarredFetch';
import type {
AlbumBrowseFetchCallbacks,
AlbumBrowsePageResult,
AlbumBrowseQuery,
GenreFilterOption,
} from './albumBrowseTypes';
import { GENRE_ALBUM_FETCH_LIMIT } from './albumBrowseTypes';
/** One local-index chunk for lazy catalog loading (All Albums slice mode). */
export async function fetchLocalAlbumCatalogChunk(
serverId: string,
query: AlbumBrowseQuery,
offset: number,
chunkSize: number,
): Promise<AlbumBrowsePageResult | null> {
const singleGenre = query.genres.length === 1;
if (query.genres.length > 1 && offset > 0) {
return { albums: [], hasMore: false };
}
const limit = singleGenre
? chunkSize
: query.genres.length > 0 && offset === 0
? GENRE_ALBUM_FETCH_LIMIT
: chunkSize;
return runLocalAlbumBrowse(serverId, query, offset, limit);
}
/** Genres in albums matching all filters except genre (for combined-filter UI). */
export async function fetchAlbumBrowseGenreOptions(
serverId: string,
indexEnabled: boolean,
query: AlbumBrowseQuery,
): Promise<GenreFilterOption[]> {
const withoutGenre: AlbumBrowseQuery = { ...query, genres: [] };
const page = await fetchAlbumBrowsePage(
serverId,
indexEnabled,
withoutGenre,
0,
GENRE_ALBUM_FETCH_LIMIT,
);
return countGenresFromAlbums(filterAlbumsByCompilation(page.albums, query.compFilter));
}
export async function fetchAlbumBrowsePage(
serverId: string,
indexEnabled: boolean,
query: AlbumBrowseQuery,
offset: number,
pageSize: number,
callbacks?: AlbumBrowseFetchCallbacks,
): Promise<AlbumBrowsePageResult> {
if (query.losslessOnly && (!indexEnabled || !serverId)) {
return { albums: [], hasMore: false };
}
if (query.starredOnly) {
return fetchStarredAlbumBrowse(serverId, indexEnabled, query, offset, pageSize, callbacks);
}
if (indexEnabled && serverId) {
const local = await runLocalAlbumBrowse(serverId, query, offset, pageSize);
if (local != null) return local;
}
return fetchAlbumBrowseNetwork(query, offset, pageSize);
}