Files
lidify/frontend/lib/download-context.tsx
2025-12-25 18:58:06 -06:00

160 lines
4.6 KiB
TypeScript

"use client";
import {
createContext,
useContext,
useState,
ReactNode,
useEffect,
} from "react";
import { useDownloadStatus } from "@/hooks/useDownloadStatus";
import { useAuth } from "@/lib/auth-context";
interface PendingDownload {
id: string;
type: "artist" | "album";
subject: string;
mbid: string; // Unique identifier for deduplication
timestamp: number;
}
interface DownloadContextType {
pendingDownloads: PendingDownload[];
downloadStatus: {
activeDownloads: any[];
recentDownloads: any[];
hasActiveDownloads: boolean;
failedDownloads: any[];
};
addPendingDownload: (
type: "artist" | "album",
subject: string,
mbid: string
) => string | null;
removePendingDownload: (id: string) => void;
removePendingByMbid: (mbid: string) => void;
isPending: (subject: string) => boolean;
isPendingByMbid: (mbid: string) => boolean;
isAnyPending: () => boolean;
}
const DownloadContext = createContext<DownloadContextType | undefined>(
undefined
);
export function DownloadProvider({ children }: { children: ReactNode }) {
const [pendingDownloads, setPendingDownloads] = useState<PendingDownload[]>(
[]
);
const { isAuthenticated } = useAuth();
const downloadStatus = useDownloadStatus(15000, isAuthenticated);
// Sync pending downloads with actual download status
useEffect(() => {
// Remove pending downloads that have completed or failed
setPendingDownloads((prev) => {
return prev.filter((pending) => {
// Check if this MBID has a job that's completed or failed
const matchingJob = [
...downloadStatus.activeDownloads,
...downloadStatus.recentDownloads,
...downloadStatus.failedDownloads,
].find((job) => job.targetMbid === pending.mbid);
// If job is completed or failed, remove from pending
if (
matchingJob &&
(matchingJob.status === "completed" ||
matchingJob.status === "failed")
) {
return false;
}
// Keep if still pending/processing or no job found yet
return true;
});
});
}, [
downloadStatus.activeDownloads,
downloadStatus.recentDownloads,
downloadStatus.failedDownloads,
]);
const addPendingDownload = (
type: "artist" | "album",
subject: string,
mbid: string
): string | null => {
// Check if already downloading this MBID
if (pendingDownloads.some((d) => d.mbid === mbid)) {
return null;
}
const id = `${Date.now()}-${Math.random()}`;
const download: PendingDownload = {
id,
type,
subject,
mbid,
timestamp: Date.now(),
};
setPendingDownloads((prev) => [...prev, download]);
return id;
};
const removePendingDownload = (id: string) => {
setPendingDownloads((prev) => prev.filter((d) => d.id !== id));
};
const removePendingByMbid = (mbid: string) => {
setPendingDownloads((prev) => prev.filter((d) => d.mbid !== mbid));
};
const isPending = (subject: string): boolean => {
return pendingDownloads.some((d) => d.subject === subject);
};
const isPendingByMbid = (mbid: string): boolean => {
// Check both pending downloads AND active download jobs
const isPendingLocal = pendingDownloads.some((d) => d.mbid === mbid);
const hasActiveJob = downloadStatus.activeDownloads.some(
(job) => job.targetMbid === mbid
);
return isPendingLocal || hasActiveJob;
};
const isAnyPending = (): boolean => {
return pendingDownloads.length > 0;
};
return (
<DownloadContext.Provider
value={{
pendingDownloads,
downloadStatus,
addPendingDownload,
removePendingDownload,
removePendingByMbid,
isPending,
isPendingByMbid,
isAnyPending,
}}
>
{children}
</DownloadContext.Provider>
);
}
export function useDownloadContext() {
const context = useContext(DownloadContext);
if (!context) {
throw new Error(
"useDownloadContext must be used within DownloadProvider"
);
}
return context;
}