fix improperly ported metadata logic

This commit is contained in:
Boof2015
2026-07-05 02:10:25 -04:00
parent 34ffda7190
commit 3a25966dde
29 changed files with 1349 additions and 110 deletions
+30 -5
View File
@@ -2,7 +2,6 @@ import { create } from 'zustand';
import type { Album, Artist, DbTrack, LibraryFolder } from '@/types/library';
import { openLibraryDb } from '@/db/database';
import {
getAlbums,
getAllTracks,
getRecentlyPlayedTracks,
getSetting,
@@ -10,6 +9,8 @@ import {
markTrackPlayed,
setSetting,
} from '@/db/queries';
import { recomputeAlbumIdentity } from '@/library/albumIdentity';
import { buildAlbumList } from '@/library/albumSummary';
import { ensureArtworkThumbnails } from '@/library/artwork';
import { buildArtistList } from '@/library/artistGrouping';
import {
@@ -37,6 +38,11 @@ const TRACK_SORT_KEY = 'library_track_sort';
const ALBUM_SORT_KEY = 'library_album_sort';
const ARTIST_SORT_KEY = 'library_artist_sort';
// Bump when the album-identity algorithm changes to re-run the whole-library
// recompute at startup. '2' = the desktop three-tier grouping port (v15 schema).
const ALBUM_GROUPING_VERSION_KEY = 'album_grouping_version';
const ALBUM_GROUPING_VERSION = '2';
const VIEW_MODES: readonly ViewMode[] = ['tracks', 'albums', 'artists', 'playlists', 'folders'];
function parseViewMode(value: string | null): ViewMode | null {
@@ -95,6 +101,7 @@ interface LibraryStore {
refresh: () => Promise<void>;
recordTrackPlayed: (path: string) => Promise<void>;
recomputeArtists: () => void;
recomputeAlbums: () => void;
setViewMode: (mode: ViewMode) => void;
setTrackSort: (sort: TrackSort) => void;
setAlbumSort: (sort: AlbumSort) => void;
@@ -148,7 +155,15 @@ export const useLibraryStore = create<LibraryStore>((set, get) => {
await useSettingsStore.getState().load();
useSettingsStore.subscribe((state, prev) => {
if (state.artistGroupingMode !== prev.artistGroupingMode) get().recomputeArtists();
if (state.includeSingles !== prev.includeSingles) get().recomputeAlbums();
});
// One-time backfill when the album-identity algorithm changes (e.g. the
// desktop three-tier grouping port): settle every track's identity key +
// display artist before the first refresh so first paint is grouped right.
if ((await getSetting(db, ALBUM_GROUPING_VERSION_KEY)) !== ALBUM_GROUPING_VERSION) {
await recomputeAlbumIdentity(db);
await setSetting(db, ALBUM_GROUPING_VERSION_KEY, ALBUM_GROUPING_VERSION);
}
// Restore view preferences before the first render of the library screen.
const [savedViewMode, savedTrackSort, savedAlbumSort, savedArtistSort] =
await Promise.all([
@@ -189,9 +204,8 @@ export const useLibraryStore = create<LibraryStore>((set, get) => {
refresh: async () => {
const db = await openLibraryDb();
const [tracks, albums, folders, totalTrackCount, recentlyPlayedTracks] = await Promise.all([
const [tracks, folders, totalTrackCount, recentlyPlayedTracks] = await Promise.all([
getAllTracks(db),
getAlbums(db),
loadFolders(),
getTrackCount(db),
getRecentlyPlayedTracks(db),
@@ -201,8 +215,11 @@ export const useLibraryStore = create<LibraryStore>((set, get) => {
} catch {
// Missing thumbnails should not prevent the library itself from loading.
}
// The artist list is derived in JS so it can honor the grouping mode.
const artists = buildArtistList(tracks, useSettingsStore.getState().artistGroupingMode);
// Album + artist lists are derived in JS: albums for the desktop-parity
// display picks + singles eligibility, artists to honor the grouping mode.
const settings = useSettingsStore.getState();
const albums = buildAlbumList(tracks, { includeSingles: settings.includeSingles });
const artists = buildArtistList(tracks, settings.artistGroupingMode);
set({ tracks, recentlyPlayedTracks, albums, artists, folders, totalTrackCount });
// Playlist counts/missing states depend on tracks — keep them in step.
await usePlaylistStore.getState().refresh();
@@ -223,6 +240,14 @@ export const useLibraryStore = create<LibraryStore>((set, get) => {
artists: buildArtistList(state.tracks, useSettingsStore.getState().artistGroupingMode),
})),
// Rebuild the album list from in-memory tracks (e.g. on singles-toggle change).
recomputeAlbums: () =>
set((state) => ({
albums: buildAlbumList(state.tracks, {
includeSingles: useSettingsStore.getState().includeSingles,
}),
})),
setViewMode: (viewMode) => {
set({ viewMode });
persistSetting(VIEW_MODE_KEY, viewMode);
+3
View File
@@ -12,6 +12,7 @@ import {
deleteFavoritesByPathPrefix,
deleteRemotePlaylistsBySource,
} from '@/db/playlistQueries';
import { recomputeAlbumIdentity } from '@/library/albumIdentity';
import {
deleteRemoteSource,
getRemoteSource,
@@ -256,6 +257,8 @@ export const useRemoteSourcesStore = create<RemoteSourcesStore>((set, get) => ({
// `${type}://${id}/` path prefix).
await deleteRemotePlaylistsBySource(db, id);
await deleteFavoritesByPathPrefix(db, `${source.type}://${id}/`);
// Removals can regroup albums (compilation heuristic is cross-track).
await recomputeAlbumIdentity(db);
}
await deleteRemoteSource(db, id);
await deleteRemoteSecret(id);
+15 -1
View File
@@ -9,6 +9,7 @@ import type { ArtistGroupingMode } from '@/library/artistGrouping';
* subscribes here to recompute the artist list when the grouping mode changes.
*/
const ARTIST_GROUPING_KEY = 'artist_grouping_mode';
const INCLUDE_SINGLES_KEY = 'album_include_singles';
const SCOPE_MODE_KEY = 'scope_mode';
const SCOPE_STAGE_VISIBLE_KEY = 'scope_stage_visible';
@@ -29,17 +30,21 @@ function parseBoolean(value: string | null): boolean {
interface SettingsStore {
artistGroupingMode: ArtistGroupingMode;
/** Show 1-track albums in the Albums view (desktop parity default: hidden). */
includeSingles: boolean;
scopeMode: ScopeMode;
scopeStageVisible: boolean;
loaded: boolean;
load: () => Promise<void>;
setArtistGroupingMode: (mode: ArtistGroupingMode) => Promise<void>;
setIncludeSingles: (include: boolean) => Promise<void>;
setScopeMode: (mode: ScopeMode) => Promise<void>;
setScopeStageVisible: (visible: boolean) => Promise<void>;
}
export const useSettingsStore = create<SettingsStore>((set, get) => ({
artistGroupingMode: 'astra',
includeSingles: false,
scopeMode: 'spectrum',
scopeStageVisible: false,
loaded: false,
@@ -47,13 +52,15 @@ export const useSettingsStore = create<SettingsStore>((set, get) => ({
load: async () => {
if (get().loaded) return;
const db = await openLibraryDb();
const [grouping, scope, scopeStageVisible] = await Promise.all([
const [grouping, includeSingles, scope, scopeStageVisible] = await Promise.all([
getSetting(db, ARTIST_GROUPING_KEY),
getSetting(db, INCLUDE_SINGLES_KEY),
getSetting(db, SCOPE_MODE_KEY),
getSetting(db, SCOPE_STAGE_VISIBLE_KEY),
]);
set({
artistGroupingMode: parseGroupingMode(grouping),
includeSingles: parseBoolean(includeSingles),
scopeMode: parseScopeMode(scope),
scopeStageVisible: parseBoolean(scopeStageVisible),
loaded: true,
@@ -67,6 +74,13 @@ export const useSettingsStore = create<SettingsStore>((set, get) => ({
await setSetting(db, ARTIST_GROUPING_KEY, mode);
},
setIncludeSingles: async (include) => {
if (get().includeSingles === include) return;
set({ includeSingles: include });
const db = await openLibraryDb();
await setSetting(db, INCLUDE_SINGLES_KEY, include ? 'true' : 'false');
},
setScopeMode: async (mode) => {
if (get().scopeMode === mode) return;
set({ scopeMode: mode });