mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 23:05:46 +00:00
7afddf7b84
* feat(library): race local index vs network on browse text search Wire Artists, Composers, Tracks, and SearchResults to parallel local FTS and network search3 with graceful fallback when remote fails while the index is ready. * feat(library): local browse for albums/artists and dev race logging Serve All Albums and Artists catalog from the local index when ready, with network fallback. Log browse text-search race outcomes to DevTools (`[psysonic][library] browse-race …`) including winner, timings, and hits. * docs(changelog): note PR #847 browse local index race and catalog paths * refactor(library): unify DevTools search log format Live Search, Advanced Search, and browse races emit one-line `search [surface] …` entries via formatLibrarySearchLine (DEV only).
69 lines
2.3 KiB
TypeScript
69 lines
2.3 KiB
TypeScript
import type { SubsonicArtist } from '../api/subsonicTypes';
|
|
import { useEffect, useRef, useState } from 'react';
|
|
import {
|
|
BROWSE_TEXT_DEBOUNCE_NETWORK_MS,
|
|
BROWSE_TEXT_DEBOUNCE_RACE_MS,
|
|
browseRaceCountsArtists,
|
|
raceBrowseWithLocalFallback,
|
|
runLocalBrowseArtists,
|
|
runNetworkBrowseArtists,
|
|
type LibrarySearchSurface,
|
|
} from '../utils/library/browseTextSearch';
|
|
|
|
/**
|
|
* Debounced artist/composer name search with local-vs-network race when the
|
|
* library index is enabled. Returns `textSearchArtists` when a raced query is
|
|
* active; callers should pass `effectiveFilter` (empty while raced) into their
|
|
* local filter hook so the query is not applied twice.
|
|
*/
|
|
export function useBrowseArtistTextSearch(
|
|
filter: string,
|
|
indexEnabled: boolean,
|
|
serverId: string | null | undefined,
|
|
surface: LibrarySearchSurface = 'artists_browse',
|
|
) {
|
|
const [debouncedFilter, setDebouncedFilter] = useState('');
|
|
const [textSearchArtists, setTextSearchArtists] = useState<SubsonicArtist[] | null>(null);
|
|
const [textSearchLoading, setTextSearchLoading] = useState(false);
|
|
const searchGenRef = useRef(0);
|
|
|
|
useEffect(() => {
|
|
const ms = indexEnabled ? BROWSE_TEXT_DEBOUNCE_RACE_MS : BROWSE_TEXT_DEBOUNCE_NETWORK_MS;
|
|
const timer = window.setTimeout(() => setDebouncedFilter(filter.trim()), ms);
|
|
return () => window.clearTimeout(timer);
|
|
}, [filter, indexEnabled]);
|
|
|
|
useEffect(() => {
|
|
const q = debouncedFilter;
|
|
if (!q || !indexEnabled || !serverId) {
|
|
setTextSearchArtists(null);
|
|
setTextSearchLoading(false);
|
|
return;
|
|
}
|
|
|
|
const gen = ++searchGenRef.current;
|
|
const isStale = () => gen !== searchGenRef.current;
|
|
setTextSearchLoading(true);
|
|
|
|
void (async () => {
|
|
const outcome = await raceBrowseWithLocalFallback(
|
|
isStale,
|
|
() => runLocalBrowseArtists(serverId, q),
|
|
() => runNetworkBrowseArtists(q),
|
|
{
|
|
surface,
|
|
query: q,
|
|
indexEnabled,
|
|
counts: browseRaceCountsArtists,
|
|
},
|
|
);
|
|
if (isStale()) return;
|
|
setTextSearchArtists(outcome?.result ?? null);
|
|
setTextSearchLoading(false);
|
|
})();
|
|
}, [debouncedFilter, indexEnabled, serverId, surface]);
|
|
|
|
const effectiveFilter = textSearchArtists != null ? '' : filter;
|
|
return { textSearchArtists, textSearchLoading, effectiveFilter };
|
|
}
|