49 lines
1.7 KiB
JavaScript
49 lines
1.7 KiB
JavaScript
import { useState, useCallback } from "react";
|
|
|
|
export function useDownloadQueue(maxConcurrent = 3) {
|
|
const [queue, setQueue] = useState([]); // queued tasks
|
|
const [activeCount, setActiveCount] = useState(0);
|
|
const [statuses, setStatuses] = useState({}); // { id: "idle"|"queued"|"downloading"|"done"|"failed" }
|
|
|
|
const scheduleNext = useCallback(() => {
|
|
setQueue((queueCurr) => {
|
|
if (activeCount >= maxConcurrent || queueCurr.length === 0)
|
|
return queueCurr;
|
|
|
|
const [task, ...rest] = queueCurr;
|
|
|
|
setStatuses((s) => ({ ...s, [task.id]: "downloading" }));
|
|
setActiveCount((c) => c + 1);
|
|
|
|
// Run the download fully in async IIFE
|
|
(async () => {
|
|
try {
|
|
await task.downloadFn(); // triggers fetch + blob + <a> click
|
|
setStatuses((s) => ({ ...s, [task.id]: "done" }));
|
|
} catch (err) {
|
|
console.error(err);
|
|
setStatuses((s) => ({ ...s, [task.id]: "failed" }));
|
|
} finally {
|
|
setActiveCount((c) => c - 1);
|
|
scheduleNext(); // promote next in queue
|
|
}
|
|
})();
|
|
|
|
return rest;
|
|
});
|
|
}, [activeCount, maxConcurrent]);
|
|
|
|
const startDownload = useCallback(
|
|
(id, downloadFn) => {
|
|
setStatuses((s) => ({ ...s, [id]: "queued" }));
|
|
setQueue((curr) => [...curr, { id, downloadFn }]);
|
|
scheduleNext();
|
|
},
|
|
[scheduleNext]
|
|
);
|
|
|
|
const getStatus = useCallback((id) => statuses[id] || "idle", [statuses]);
|
|
|
|
return { startDownload, getStatus };
|
|
}
|