diff --git a/routes/api/task.ts b/routes/api/task.ts index e2ef666..efdffa2 100644 --- a/routes/api/task.ts +++ b/routes/api/task.ts @@ -6,6 +6,7 @@ import { DiscriminatedUnion } from "../../utils.ts"; type EventMap = { close: Record; new_download_task: { gid: number; token: string }; + new_export_zip_task: { gid: number; output?: string }; }; type EventData = DiscriminatedUnion<"type", EventMap>; @@ -37,6 +38,8 @@ export const handler: Handlers = { socket.close(); } else if (d.type == "new_download_task") { t.add_download_task(d.gid, d.token); + } else if (d.type == "new_export_zip_task") { + t.add_export_zip_task(d.gid, d.output); } } catch (_) { null; diff --git a/task.ts b/task.ts index 6edfe4f..c6d10ad 100644 --- a/task.ts +++ b/task.ts @@ -24,9 +24,15 @@ export type TaskExportZipProgress = { total_page: number; }; -export type TaskProgressType = { - [TaskType.Download]: TaskDownloadProgess; - [TaskType.ExportZip]: TaskExportZipProgress; +type TaskId> = { + [P in keyof T]: ({ + task_id: number; + } & T[P]) extends infer U ? { [Q in keyof U]: U[Q] } : never; }; +export type TaskProgressType = TaskId<{ + [TaskType.Download]: TaskDownloadProgess; + [TaskType.ExportZip]: TaskExportZipProgress; +}>; + export type TaskProgress = DiscriminatedUnion<"type", TaskProgressType>; diff --git a/task_manager.ts b/task_manager.ts index 880fab9..b6ef74f 100644 --- a/task_manager.ts +++ b/task_manager.ts @@ -3,7 +3,7 @@ import { Config } from "./config.ts"; import { EhDb } from "./db.ts"; import { check_running } from "./pid_check.ts"; import { add_exit_handler } from "./signal_handler.ts"; -import { Task, TaskProgress, TaskType } from "./task.ts"; +import { Task, TaskProgress, TaskProgressType, TaskType } from "./task.ts"; import { download_task } from "./tasks/download.ts"; import { DEFAULT_EXPORT_ZIP_CONFIG, @@ -157,6 +157,15 @@ export class TaskManager extends EventTarget { dispatchEvent(type: T, detail: EventMap[T]) { return super.dispatchEvent(new CustomEvent(type, { detail })); } + dispatchTaskProgressEvent( + type: T, + detail: TaskProgressType[T], + ) { + return this.dispatchEvent( + "task_progress", + { type, ...detail }, + ); + } force_abort(reason?: unknown) { this.#force_abort.abort(reason); } @@ -208,6 +217,7 @@ export class TaskManager extends EventTarget { } run_task(task: Task) { this.#check_closed(); + this.dispatchEvent("task_started", task); if (task.type == TaskType.Download) { this.running_tasks.set( task.id, @@ -218,6 +228,7 @@ export class TaskManager extends EventTarget { this.cfg, this.#abort.signal, this.#force_abort.signal, + this, ), ); } else if (task.type == TaskType.ExportZip) { @@ -232,10 +243,10 @@ export class TaskManager extends EventTarget { this.cfg, this.#abort.signal, cfg, + this, ), ); } - this.dispatchEvent("task_started", task); } async waiting_unfinished_task() { while (1) { diff --git a/tasks/download.ts b/tasks/download.ts index 5ffdfbc..4e0e4eb 100644 --- a/tasks/download.ts +++ b/tasks/download.ts @@ -2,7 +2,8 @@ import { assert } from "std/testing/asserts.ts"; import { Client } from "../client.ts"; import { Config } from "../config.ts"; import { EhDb } from "../db.ts"; -import { Task } from "../task.ts"; +import { Task, TaskDownloadProgess, TaskType } from "../task.ts"; +import { TaskManager } from "../task_manager.ts"; import { asyncFilter, promiseState, @@ -19,11 +20,23 @@ class DownloadManager { #max_download_count; #running_tasks: Promise[]; #has_failed_task = false; - constructor(cfg: Config, abort: AbortSignal, force_abort: AbortSignal) { + #progress: TaskDownloadProgess; + #task: Task; + #manager: TaskManager; + constructor( + cfg: Config, + abort: AbortSignal, + force_abort: AbortSignal, + task: Task, + manager: TaskManager, + ) { this.#max_download_count = cfg.max_download_img_count; this.#running_tasks = []; this.#abort = abort; this.#force_abort = force_abort; + this.#progress = { total_page: 0, downloaded_page: 0 }; + this.#task = task; + this.#manager = manager; } async #check_tasks() { this.#running_tasks = await asyncFilter( @@ -33,11 +46,21 @@ class DownloadManager { if (s.status === PromiseStatus.Rejected) { if (!this.#force_abort.aborted) console.log(s.reason); this.#has_failed_task = true; + } else if (s.status === PromiseStatus.Fulfilled) { + this.#progress.downloaded_page += 1; + this.#sendEvent(); } return s.status === PromiseStatus.Pending; }, ); } + #sendEvent() { + return this.#manager.dispatchTaskProgressEvent(TaskType.Download, { + task_id: this.#task.id, + downloaded_page: this.#progress.downloaded_page, + total_page: this.#progress.total_page, + }); + } async add_new_task(f: () => Promise) { while (1) { if (this.#abort.aborted) break; @@ -59,6 +82,10 @@ class DownloadManager { await sleep(10); } } + set_total_page(page: number) { + this.#progress.total_page = page; + this.#sendEvent(); + } } export async function download_task( @@ -68,6 +95,7 @@ export async function download_task( cfg: Config, abort: AbortSignal, force_abort: AbortSignal, + manager: TaskManager, ) { console.log("Started to download gallery", task.gid); const gdatas = await client.fetchGalleryMetadataByAPI([ @@ -82,9 +110,10 @@ export async function download_task( await db.add_gtag(task.gid, new Set(gdata.tags)); const base_path = join(cfg.base, task.gid.toString()); await sure_dir(base_path); - const m = new DownloadManager(cfg, abort, force_abort); + const m = new DownloadManager(cfg, abort, force_abort, task, manager); if (cfg.mpv) { const mpv = await client.fetchMPVPage(task.gid, task.token); + m.set_total_page(mpv.pagecount); for (const i of mpv.imagelist) { if (abort.aborted) break; await m.add_new_task(async () => { diff --git a/tasks/export_zip.ts b/tasks/export_zip.ts index 526fed7..4424d46 100644 --- a/tasks/export_zip.ts +++ b/tasks/export_zip.ts @@ -3,7 +3,8 @@ import { Uint8ArrayReader, ZipWriter } from "zipjs/index.js"; import { EhDb } from "../db.ts"; import { addZero, asyncForEach, filterFilename } from "../utils.ts"; import { Config } from "../config.ts"; -import { Task } from "../task.ts"; +import { Task, TaskExportZipProgress, TaskType } from "../task.ts"; +import { TaskManager } from "../task_manager.ts"; export type ExportZipConfig = { output?: string; @@ -17,10 +18,23 @@ export async function export_zip( cfg: Config, signal: AbortSignal, ecfg: ExportZipConfig, + manager: TaskManager, ) { const gid = task.gid; const g = db.get_gmeta_by_gid(gid); if (!g) throw Error("Gallery not found in database."); + const progress: TaskExportZipProgress = { + total_page: g.filecount, + added_page: 0, + }; + const sendEvent = () => { + manager.dispatchTaskProgressEvent(TaskType.ExportZip, { + task_id: task.id, + added_page: progress.added_page, + total_page: progress.total_page, + }); + }; + sendEvent(); const output = ecfg.output === undefined ? join(cfg.base, filterFilename(g.title + ".zip")) : ecfg.output; @@ -47,6 +61,8 @@ export async function export_zip( { signal }, ); } + progress.added_page += 1; + sendEvent(); }, ); await z.close();