This commit is contained in:
2023-06-08 22:47:46 +08:00
parent 2002dd4749
commit 7b5ba18d89
11 changed files with 193 additions and 34 deletions

5
db.ts
View File

@@ -524,6 +524,11 @@ export class EhDb {
);
return s.length ? s[0] : undefined;
}
get_tasks() {
return this.transaction(() =>
this.db.queryEntries<Task>("SELECT * FROM task;")
);
}
get_tasks_by_pid(pid: number) {
return this.transaction(() =>
this.db.queryEntries<Task>("SELECT * FROM task WHERE pid = ?;", [

2
deno.lock generated
View File

@@ -197,6 +197,7 @@
"https://esm.sh/*@preact/[email protected]": "f1591d7185a00b6f96fdf5f72a99bb7dde37c0e946c8854da71db6b99d430947",
"https://esm.sh/*[email protected]": "88ec8d8706b6a3f1e0fdad3862a2690dcd9b350d87bdc8e7bd0e27fbc0f7d29e",
"https://esm.sh/[email protected]/": "16d82ee0a75451f75b42d9a20db4da0ccae7ccc8cc09a41c73b4488aba010b94",
"https://esm.sh/gh/SortableJS/[email protected]/Sortable.min.js": "0a3e3cf471bf4566d7a3f9823b1d0d3b2bd075404e93419cd4074bd23310a9b0",
"https://esm.sh/[email protected]/Button": "bc60923d511c6e2e33a7064339b3e643a9c15e3ef232ab063ef570af2ef83dc8",
"https://esm.sh/[email protected]/Checkbox": "bf34f5cd8c6d015916d854d91aab2caf115463e97be9a461f8dd3370ea11a49c",
"https://esm.sh/[email protected]/Dialog": "b0ff8da9c770456748f7e065fecda2fc90f5364ea66cae75ff5f51d57f6a87eb",
@@ -319,6 +320,7 @@
"https://esm.sh/v124/[email protected]/deno/twind.mjs": "ac4bf729653ee66349a518ee4949f22e84b7f626e8f74de1066e798a7c1ca12a",
"https://esm.sh/v124/[email protected]/sheets/sheets.d.ts": "9cd4663d180023e49d9379777c8926347f3f79bf3bee546e7e5be1524fe55e70",
"https://esm.sh/v124/[email protected]/twind.d.ts": "48c49da7d770f1236ec8a9397af053e6fb5a2bedacf431f2189eeecc1468c01b",
"https://esm.sh/v125/gh/SortableJS/[email protected]/denonext/Sortable.min.js": "aeba191bb6622c4ad41ae5d8f5d4dd6bf15074f264cb3640ed223fbaf052263b",
"https://raw.githubusercontent.com/lucacasonato/esbuild_deno_loader/8031f71afa1bbcd3237a94b11f53a2e5c5c0e7bf/deps.ts": "b7248e5b750be62613a9417f407e65ed43726d83b11f9631d6dbb58634bbd7d1",
"https://raw.githubusercontent.com/lucacasonato/esbuild_deno_loader/8031f71afa1bbcd3237a94b11f53a2e5c5c0e7bf/mod.ts": "3e507379372361162f93325a216b86f6098defb5bb60144555b507bca26d061f",
"https://raw.githubusercontent.com/lucacasonato/esbuild_deno_loader/8031f71afa1bbcd3237a94b11f53a2e5c5c0e7bf/src/deno.ts": "71bee6b14e72ca193c0686d8b4f1f47d639a64745b6f5c7576f7a3616f436f57",

View File

@@ -10,6 +10,7 @@ import * as $3 from "./routes/api/task.ts";
import * as $4 from "./routes/index.tsx";
import * as $$0 from "./islands/Container.tsx";
import * as $$1 from "./islands/Settings.tsx";
import * as $$2 from "./islands/TaskManager.tsx";
const manifest = {
routes: {
@@ -22,6 +23,7 @@ const manifest = {
islands: {
"./islands/Container.tsx": $$0,
"./islands/Settings.tsx": $$1,
"./islands/TaskManager.tsx": $$2,
},
baseUrl: import.meta.url,
config,

View File

@@ -14,6 +14,7 @@
"twind/": "https://esm.sh/[email protected]/",
"$std/": "https://deno.land/[email protected]/",
"preact-material-components/": "https://esm.sh/[email protected]/",
"accept-language-parser/": "https://esm.sh/[email protected]/"
"accept-language-parser/": "https://esm.sh/[email protected]/",
"sortable": "https://esm.sh/gh/SortableJS/[email protected]/Sortable.min.js"
}
}

View File

@@ -8,6 +8,7 @@ import StyleSheet from "../components/StyleSheet.tsx";
import { GlobalCtx } from "../components/GlobalContext.tsx";
import Settings from "./Settings.tsx";
import t, { i18n_map, I18NMap } from "../server/i18n.ts";
import TaskManager from "./TaskManager.tsx";
export type ContainerProps = {
i18n: I18NMap;
@@ -76,6 +77,14 @@ export default class Container extends Component<ContainerProps> {
>
<Icon>home</Icon>
</List.Item>
<List.Item
onClick={() => {
set_display(false);
set_state("#/task_manager");
}}
>
<Icon>task</Icon>
</List.Item>
<List.Item
onClick={() => {
set_display(false);
@@ -87,6 +96,7 @@ export default class Container extends Component<ContainerProps> {
</List>
<div class="main">
<Settings show={state === "#/settings"} />
<TaskManager show={state === "#/task_manager"} />
</div>
</div>
);

79
islands/TaskManager.tsx Normal file
View File

@@ -0,0 +1,79 @@
import { Component, ContextType } from "preact";
import { useEffect, useRef, useState } from "preact/hooks";
import { GlobalCtx } from "../components/GlobalContext.tsx";
import { TaskDetail, TaskStatus } from "../task.ts";
import { Sortable } from "sortable";
import { TaskClientSocketData, TaskServerSocketData } from "../server/task.ts";
import { get_ws_host } from "../server/utils.ts";
export type TaskManagerProps = {
show: boolean;
};
export default class TaskManager extends Component<TaskManagerProps> {
static contextType = GlobalCtx;
declare context: ContextType<typeof GlobalCtx>;
render() {
if (!this.props.show) return null;
const [tasks, set_tasks] = useState<Map<number, TaskDetail>>(new Map());
const ul = useRef<HTMLDivElement>();
useEffect(() => {
new Sortable(ul.current, {
onSort: (evt: CustomEvent) => {
console.log(evt);
},
});
const ws = new WebSocket(`${get_ws_host()}/api/task`);
console.log(ws);
function sendMessage(mes: TaskClientSocketData) {
ws.send(JSON.stringify(mes));
}
ws.onopen = () => {
sendMessage({ type: "task_list" });
};
ws.onmessage = (e) => {
const t: TaskServerSocketData = JSON.parse(e.data);
if (t.type == "close") {
ws.close();
} else if (t.type == "tasks") {
set_tasks((tasks) => {
t.tasks.forEach((ta) => {
tasks.set(ta.id, {
base: ta,
status: t.running.includes(ta.id)
? TaskStatus.Running
: TaskStatus.Wait,
});
});
this.forceUpdate();
return tasks;
});
} else if (t.type == "new_task") {
set_tasks((tasks) => {
tasks.set(t.detail.id, {
base: t.detail,
status: TaskStatus.Wait,
});
this.forceUpdate();
return tasks;
});
}
};
self.addEventListener("beforeunload", () => {
sendMessage({ type: "close" });
});
}, []);
console.log(tasks.size);
return (
<div class="task_manager">
<div
id="task-list"
// @ts-ignore checked
ref={ul}
>
{Array.from(tasks.keys()).map((k) => <div>{k}</div>)}
</div>
</div>
);
}
}

View File

@@ -1,15 +1,10 @@
import { Handlers } from "$fresh/server.ts";
import { get_task_manager } from "../../server.ts";
import { Task, TaskProgress } from "../../task.ts";
import { DiscriminatedUnion } from "../../utils.ts";
type EventMap = {
close: Record<PropertyKey, never>;
new_download_task: { gid: number; token: string };
new_export_zip_task: { gid: number; output?: string };
};
type EventData = DiscriminatedUnion<"type", EventMap>;
import {
TaskClientSocketData,
TaskServerSocketData,
} from "../../server/task.ts";
export const handler: Handlers<Task[]> = {
GET(req, _ctx) {
@@ -24,6 +19,9 @@ export const handler: Handlers<Task[]> = {
t.removeEventListener("task_finished", handle);
t.removeEventListener("task_progress", handle);
};
function sendMessage(mes: TaskServerSocketData) {
socket.send(JSON.stringify(mes));
}
socket.onclose = () => {
removeListener();
};
@@ -33,13 +31,22 @@ export const handler: Handlers<Task[]> = {
};
socket.onmessage = (e) => {
try {
const d: EventData = JSON.parse(e.data);
const d: TaskClientSocketData = JSON.parse(e.data);
if (d.type == "close") {
sendMessage({ type: "close" });
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);
} else if (d.type == "task_list") {
t.get_task_list().then((tasks) => {
sendMessage({
type: "tasks",
tasks,
running: t.get_running_task(),
});
});
}
} catch (_) {
null;

18
server/task.ts Normal file
View File

@@ -0,0 +1,18 @@
import { Task } from "../task.ts";
import { TaskEventData } from "../task_manager.ts";
import { DiscriminatedUnion } from "../utils.ts";
export type TaskServerSocketData = TaskEventData | { type: "close" } | {
type: "tasks";
tasks: Task[];
running: number[];
};
type EventMap = {
new_download_task: { gid: number; token: string };
new_export_zip_task: { gid: number; output?: string };
};
export type TaskClientSocketData = DiscriminatedUnion<"type", EventMap> | {
type: "close";
} | { type: "task_list" };

4
server/utils.ts Normal file
View File

@@ -0,0 +1,4 @@
export function get_ws_host() {
const protocol = document.location.protocol === "https:" ? "wss:" : "ws:";
return `${protocol}//${document.location.host}`;
}

22
task.ts
View File

@@ -5,9 +5,9 @@ export enum TaskType {
ExportZip,
}
export type Task = {
export type Task<T extends TaskType = TaskType> = {
id: number;
type: TaskType;
type: T;
gid: number;
token: string;
pid: number;
@@ -30,9 +30,23 @@ type TaskId<T extends Record<PropertyKey, unknown>> = {
} & T[P]) extends infer U ? { [Q in keyof U]: U[Q] } : never;
};
export type TaskProgressType = TaskId<{
export type TaskProgressBasicType = {
[TaskType.Download]: TaskDownloadProgess;
[TaskType.ExportZip]: TaskExportZipProgress;
}>;
};
export type TaskProgressType = TaskId<TaskProgressBasicType>;
export type TaskProgress = DiscriminatedUnion<"type", TaskProgressType>;
export enum TaskStatus {
Wait,
Running,
Finished,
}
export type TaskDetail<T extends TaskType = TaskType> = {
base: Task<T>;
progress?: TaskProgressBasicType[T];
status: TaskStatus;
};

View File

@@ -33,12 +33,17 @@ type Detail<T extends Record<PropertyKey, unknown>> = {
export type TaskEventData = DiscriminatedUnion<"type", Detail<EventMap>>;
type RunningTask = {
task: Promise<Task>;
base: Task;
};
export class TaskManager extends EventTarget {
#closed = false;
cfg;
client;
db;
running_tasks: Map<number, Promise<Task>>;
running_tasks: Map<number, RunningTask>;
max_task_count;
#abort;
#force_abort;
@@ -131,7 +136,7 @@ export class TaskManager extends EventTarget {
this.#check_closed();
const removed_task: number[] = [];
for (const [id, task] of this.running_tasks) {
const status = await promiseState(task);
const status = await promiseState(task.task);
if (status.status == PromiseStatus.Fulfilled && status.value) {
removed_task.push(id);
await this.db.delete_task(status.value);
@@ -175,6 +180,12 @@ export class TaskManager extends EventTarget {
get force_aborts() {
return this.#force_abort.signal;
}
get_running_task() {
return Array.from(this.running_tasks.keys());
}
get_task_list() {
return this.db.get_tasks();
}
// @ts-ignore Checked type
removeEventListener<T extends keyof EventMap>(
type: T,
@@ -221,15 +232,18 @@ export class TaskManager extends EventTarget {
if (task.type == TaskType.Download) {
this.running_tasks.set(
task.id,
download_task(
task,
this.client,
this.db,
this.cfg,
this.#abort.signal,
this.#force_abort.signal,
this,
),
{
task: download_task(
task,
this.client,
this.db,
this.cfg,
this.#abort.signal,
this.#force_abort.signal,
this,
),
base: task,
},
);
} else if (task.type == TaskType.ExportZip) {
const cfg: ExportZipConfig = task.details
@@ -237,14 +251,17 @@ export class TaskManager extends EventTarget {
: DEFAULT_EXPORT_ZIP_CONFIG;
this.running_tasks.set(
task.id,
export_zip(
task,
this.db,
this.cfg,
this.#abort.signal,
cfg,
this,
),
{
task: export_zip(
task,
this.db,
this.cfg,
this.#abort.signal,
cfg,
this,
),
base: task,
},
);
}
}