121 lines
2.9 KiB
TypeScript
121 lines
2.9 KiB
TypeScript
import { useCallback, useRef, useState } from "react";
|
|
import {
|
|
getAllSourceCodes,
|
|
getDefaultFetchUrl,
|
|
} from "../adapters/bestiary-index-adapter.js";
|
|
|
|
const BATCH_SIZE = 6;
|
|
|
|
export interface BulkImportState {
|
|
readonly status: "idle" | "loading" | "complete" | "partial-failure";
|
|
readonly total: number;
|
|
readonly completed: number;
|
|
readonly failed: number;
|
|
}
|
|
|
|
const IDLE_STATE: BulkImportState = {
|
|
status: "idle",
|
|
total: 0,
|
|
completed: 0,
|
|
failed: 0,
|
|
};
|
|
|
|
interface BulkImportHook {
|
|
state: BulkImportState;
|
|
startImport: (
|
|
baseUrl: string,
|
|
fetchAndCacheSource: (sourceCode: string, url: string) => Promise<void>,
|
|
isSourceCached: (sourceCode: string) => Promise<boolean>,
|
|
refreshCache: () => Promise<void>,
|
|
) => void;
|
|
reset: () => void;
|
|
}
|
|
|
|
export function useBulkImport(): BulkImportHook {
|
|
const [state, setState] = useState<BulkImportState>(IDLE_STATE);
|
|
const countersRef = useRef({ completed: 0, failed: 0 });
|
|
|
|
const startImport = useCallback(
|
|
(
|
|
baseUrl: string,
|
|
fetchAndCacheSource: (sourceCode: string, url: string) => Promise<void>,
|
|
isSourceCached: (sourceCode: string) => Promise<boolean>,
|
|
refreshCache: () => Promise<void>,
|
|
) => {
|
|
const allCodes = getAllSourceCodes();
|
|
const total = allCodes.length;
|
|
|
|
countersRef.current = { completed: 0, failed: 0 };
|
|
setState({ status: "loading", total, completed: 0, failed: 0 });
|
|
|
|
(async () => {
|
|
const cacheChecks = await Promise.all(
|
|
allCodes.map(async (code) => ({
|
|
code,
|
|
cached: await isSourceCached(code),
|
|
})),
|
|
);
|
|
|
|
const alreadyCached = cacheChecks.filter((c) => c.cached).length;
|
|
const uncached = cacheChecks.filter((c) => !c.cached);
|
|
|
|
countersRef.current.completed = alreadyCached;
|
|
|
|
if (uncached.length === 0) {
|
|
setState({
|
|
status: "complete",
|
|
total,
|
|
completed: total,
|
|
failed: 0,
|
|
});
|
|
return;
|
|
}
|
|
|
|
setState((s) => ({ ...s, completed: alreadyCached }));
|
|
|
|
for (let i = 0; i < uncached.length; i += BATCH_SIZE) {
|
|
const batch = uncached.slice(i, i + BATCH_SIZE);
|
|
await Promise.allSettled(
|
|
batch.map(async ({ code }) => {
|
|
const url = getDefaultFetchUrl(code, baseUrl);
|
|
try {
|
|
await fetchAndCacheSource(code, url);
|
|
countersRef.current.completed++;
|
|
} catch (err) {
|
|
countersRef.current.failed++;
|
|
console.warn(
|
|
`[bulk-import] FAILED ${code} (${url}):`,
|
|
err instanceof Error ? err.message : err,
|
|
);
|
|
}
|
|
setState({
|
|
status: "loading",
|
|
total,
|
|
completed: countersRef.current.completed,
|
|
failed: countersRef.current.failed,
|
|
});
|
|
}),
|
|
);
|
|
}
|
|
|
|
await refreshCache();
|
|
|
|
const { completed, failed } = countersRef.current;
|
|
setState({
|
|
status: failed > 0 ? "partial-failure" : "complete",
|
|
total,
|
|
completed,
|
|
failed,
|
|
});
|
|
})();
|
|
},
|
|
[],
|
|
);
|
|
|
|
const reset = useCallback(() => {
|
|
setState(IDLE_STATE);
|
|
}, []);
|
|
|
|
return { state, startImport, reset };
|
|
}
|