mirror of
https://github.com/moku-project/Moku.git
synced 2026-06-13 17:29:55 -05:00
Chore: Restructure Repository for SvelteKit
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
import type { DownloadQueueItem } from "@types/index";
|
||||
|
||||
const RETRY_DELAY_MS = 20_000;
|
||||
|
||||
export interface AutoRetryHandle {
|
||||
stop: () => void;
|
||||
}
|
||||
|
||||
export function startAutoRetry(
|
||||
getQueue: () => DownloadQueueItem[],
|
||||
isRunning: () => boolean,
|
||||
retryErrored: () => Promise<void>,
|
||||
): AutoRetryHandle {
|
||||
let stopped = false;
|
||||
let timer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
async function tick() {
|
||||
if (stopped) return;
|
||||
|
||||
const queue = getQueue();
|
||||
const errored = queue.filter(i => i.state === "ERROR");
|
||||
const active = queue.filter(i => i.state !== "ERROR");
|
||||
|
||||
if (errored.length > 0 && active.length === 0 && !isRunning()) {
|
||||
await retryErrored().catch(() => {});
|
||||
}
|
||||
|
||||
if (!stopped) timer = setTimeout(tick, RETRY_DELAY_MS);
|
||||
}
|
||||
|
||||
timer = setTimeout(tick, RETRY_DELAY_MS);
|
||||
|
||||
return {
|
||||
stop() {
|
||||
stopped = true;
|
||||
if (timer !== null) { clearTimeout(timer); timer = null; }
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import type { DownloadQueueItem, ActiveDownload } from "@types/index";
|
||||
|
||||
export function toActiveDownloads(queue: DownloadQueueItem[]): ActiveDownload[] {
|
||||
return queue.map((item) => ({
|
||||
chapterId: item.chapter.id,
|
||||
mangaId: item.chapter.mangaId,
|
||||
progress: item.progress,
|
||||
}));
|
||||
}
|
||||
|
||||
export function optimisticRemove(queue: DownloadQueueItem[], chapterId: number): DownloadQueueItem[] {
|
||||
return queue.filter((i) => i.chapter.id !== chapterId);
|
||||
}
|
||||
|
||||
export function optimisticRemoveMany(queue: DownloadQueueItem[], chapterIds: Set<number>): DownloadQueueItem[] {
|
||||
return queue.filter((i) => !chapterIds.has(i.chapter.id));
|
||||
}
|
||||
|
||||
export function isRunning(state: string | undefined): boolean {
|
||||
return state === "STARTED";
|
||||
}
|
||||
|
||||
export function getErrored(queue: DownloadQueueItem[]): DownloadQueueItem[] {
|
||||
return queue.filter((i) => i.state === "ERROR");
|
||||
}
|
||||
|
||||
export function pageProgress(progress: number, pageCount: number): { done: number; total: number } {
|
||||
return { done: Math.round(progress * pageCount), total: pageCount };
|
||||
}
|
||||
|
||||
export interface SpeedSample {
|
||||
ts: number;
|
||||
progress: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export function calcSpeed(prev: SpeedSample | null, current: SpeedSample): number | null {
|
||||
if (!prev) return null;
|
||||
const dt = (current.ts - prev.ts) / 1000;
|
||||
if (dt <= 0) return null;
|
||||
const prevDone = Math.round(prev.progress * prev.pages);
|
||||
const curDone = Math.round(current.progress * current.pages);
|
||||
const delta = curDone - prevDone;
|
||||
if (delta <= 0) return null;
|
||||
return delta / dt;
|
||||
}
|
||||
|
||||
export function estimateEta(pagesPerSec: number, queue: DownloadQueueItem[]): number | null {
|
||||
if (pagesPerSec <= 0 || queue.length === 0) return null;
|
||||
let remaining = 0;
|
||||
for (const item of queue) {
|
||||
const pages = item.chapter.pageCount ?? 0;
|
||||
remaining += pages - Math.round(item.progress * pages);
|
||||
}
|
||||
return remaining / pagesPerSec;
|
||||
}
|
||||
|
||||
export function reorderSelectedToEdge(
|
||||
queue: DownloadQueueItem[],
|
||||
selected: Set<number>,
|
||||
edge: "top" | "bottom",
|
||||
): DownloadQueueItem[] {
|
||||
const pinned = queue.filter((i) => selected.has(i.chapter.id));
|
||||
const rest = queue.filter((i) => !selected.has(i.chapter.id));
|
||||
return edge === "top" ? [...pinned, ...rest] : [...rest, ...pinned];
|
||||
}
|
||||
|
||||
const AVG_BYTES_PER_PAGE = 1_500_000;
|
||||
|
||||
export function estimateQueueBytes(queue: DownloadQueueItem[]): number {
|
||||
let total = 0;
|
||||
for (const item of queue) {
|
||||
const pages = item.chapter.pageCount ?? 0;
|
||||
const remaining = pages - Math.round(item.progress * pages);
|
||||
total += remaining * AVG_BYTES_PER_PAGE;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
export function formatEta(seconds: number): string {
|
||||
if (seconds < 60) return `~${Math.ceil(seconds)}s`;
|
||||
if (seconds < 3600) return `~${Math.ceil(seconds / 60)}m`;
|
||||
return `~${(seconds / 3600).toFixed(1)}h`;
|
||||
}
|
||||
Reference in New Issue
Block a user