Files
lidify/frontend/features/artist/hooks/useArtistData.ts
Your Name cc8d0f6969 Release v1.3.0: Multi-source downloads, audio analyzer resilience, mobile improvements
Major Features:
- Multi-source download system (Soulseek/Lidarr with fallback)
- Configurable enrichment speed control (1-5x)
- Mobile touch drag support for seek sliders
- iOS PWA media controls (Control Center, Lock Screen)
- Artist name alias resolution via Last.fm
- Circuit breaker pattern for audio analysis

Critical Fixes:
- Audio analyzer stability (non-ASCII, BrokenProcessPool, OOM)
- Discovery system race conditions and import failures
- Radio decade categorization using originalYear
- LastFM API response normalization
- Mood bucket infinite loop prevention

Security:
- Bull Board admin authentication
- Lidarr webhook signature verification
- JWT token expiration and refresh
- Encryption key validation on startup

Closes #2, #6, #9, #13, #21, #26, #31, #34, #35, #37, #40, #43
2026-01-06 20:07:33 -06:00

100 lines
3.4 KiB
TypeScript

import { useParams, useRouter } from "next/navigation";
import { toast } from "sonner";
import { useQuery } from "@tanstack/react-query";
import { queryKeys } from "@/hooks/useQueries";
import { api } from "@/lib/api";
import { useDownloadContext } from "@/lib/download-context";
import { Artist, Album, ArtistSource } from "../types";
import { useMemo, useEffect, useRef, useState } from "react";
export function useArtistData() {
const params = useParams();
const router = useRouter();
const id = params.id as string;
const { downloadStatus } = useDownloadContext();
const prevActiveCountRef = useRef(downloadStatus.activeDownloads.length);
// Use React Query - no polling needed, webhook events trigger refresh via download context
const {
data: artist,
isLoading,
error,
isError,
refetch,
} = useQuery({
queryKey: queryKeys.artist(id || ""),
queryFn: async () => {
if (!id) throw new Error("Artist ID is required");
try {
return await api.getArtist(id);
} catch (error) {
return await api.getArtistDiscovery(id);
}
},
enabled: !!id,
staleTime: 10 * 60 * 1000,
retry: 1,
});
// Refetch when downloads complete (active count decreases)
useEffect(() => {
const currentActiveCount = downloadStatus.activeDownloads.length;
if (
prevActiveCountRef.current > 0 &&
currentActiveCount < prevActiveCountRef.current
) {
// Downloads have completed, refresh data
refetch();
}
prevActiveCountRef.current = currentActiveCount;
}, [downloadStatus.activeDownloads.length, refetch]);
// Determine source from the artist data (if it came from library or discovery)
const source: ArtistSource | null = useMemo(() => {
if (!artist) return null;
return artist.id && !artist.id.includes("-") ? "library" : "discovery";
}, [artist]);
// Sort state: 'year' or 'dateAdded'
const [sortBy, setSortBy] = useState<"year" | "dateAdded">("year");
// Sort albums by year or dateAdded - memoized
const albums = useMemo(() => {
if (!artist?.albums) return [];
return [...artist.albums].sort((a, b) => {
if (sortBy === "dateAdded") {
// Sort by lastSynced (newest first, nulls last)
if (!a.lastSynced && !b.lastSynced) return 0;
if (!a.lastSynced) return 1;
if (!b.lastSynced) return -1;
return (
new Date(b.lastSynced).getTime() -
new Date(a.lastSynced).getTime()
);
} else {
// Sort by year (newest first, nulls last)
if (a.year == null && b.year == null) return 0;
if (a.year == null) return 1;
if (b.year == null) return -1;
return b.year - a.year;
}
});
}, [artist?.albums, sortBy]);
// Handle errors - only show toast once, don't auto-navigate
// The page component should handle displaying a "not found" state
// Don't call router.back() as it causes navigation loops
return {
artist,
albums,
loading: isLoading,
error: isError,
source,
sortBy,
setSortBy,
reloadArtist: refetch,
};
}