mirror of
https://github.com/Drop-OSS/drop.git
synced 2026-07-20 23:12:51 +10:00
task API
The Task API allows for an easy way to create long-lived tasks that require reporting back to user with progress/logs. It will be used in the upcoming game importing.
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* The TaskHandler setups up two-way connections to web clients and manages the state for them
|
||||
* This allows long-running tasks (like game imports and such) to report progress, success and error states
|
||||
* easily without re-inventing the wheel every time.
|
||||
*/
|
||||
|
||||
type TaskRegistryEntry = {
|
||||
runPromise: Promise<void>;
|
||||
success: boolean;
|
||||
progress: number;
|
||||
log: string[];
|
||||
error: string | undefined;
|
||||
clients: { [key: string]: boolean };
|
||||
name: string;
|
||||
};
|
||||
|
||||
class TaskHandler {
|
||||
private taskRegistry: { [key: string]: TaskRegistryEntry } = {};
|
||||
private clientRegistry: { [key: string]: PeerImpl } = {};
|
||||
|
||||
constructor() {}
|
||||
|
||||
create(task: Task) {
|
||||
let updateCollectTimeout: NodeJS.Timeout | undefined;
|
||||
|
||||
const updateAllClients = () => {
|
||||
if (updateCollectTimeout) return;
|
||||
updateCollectTimeout = setTimeout(() => {
|
||||
const taskEntry = this.taskRegistry[task.id];
|
||||
if (!taskEntry) return;
|
||||
const taskMessage: TaskMessage = {
|
||||
id: task.id,
|
||||
success: taskEntry.success,
|
||||
progress: taskEntry.progress,
|
||||
error: taskEntry.error,
|
||||
log: taskEntry.log,
|
||||
};
|
||||
for (const client of Object.keys(taskEntry.clients)) {
|
||||
if (!this.clientRegistry[client]) continue;
|
||||
this.clientRegistry[client].send(taskMessage);
|
||||
}
|
||||
updateCollectTimeout = undefined;
|
||||
}, 500);
|
||||
};
|
||||
|
||||
const progress = (progress: number) => {
|
||||
const taskEntry = this.taskRegistry[task.id];
|
||||
if (!taskEntry) return;
|
||||
this.taskRegistry[task.id].progress = progress;
|
||||
updateAllClients();
|
||||
};
|
||||
|
||||
const log = (entry: string) => {
|
||||
const taskEntry = this.taskRegistry[task.id];
|
||||
if (!taskEntry) return;
|
||||
this.taskRegistry[task.id].log.push(entry);
|
||||
updateAllClients();
|
||||
};
|
||||
|
||||
const promiseRun = task.run({ progress, log });
|
||||
promiseRun.then(() => {
|
||||
const taskEntry = this.taskRegistry[task.id];
|
||||
if (!taskEntry) return;
|
||||
this.taskRegistry[task.id].success = true;
|
||||
updateAllClients();
|
||||
});
|
||||
promiseRun.catch((error) => {
|
||||
const taskEntry = this.taskRegistry[task.id];
|
||||
if (!taskEntry) return;
|
||||
this.taskRegistry[task.id].success = false;
|
||||
this.taskRegistry[task.id].error = error;
|
||||
updateAllClients();
|
||||
});
|
||||
this.taskRegistry[task.id] = {
|
||||
name: task.name,
|
||||
runPromise: promiseRun,
|
||||
success: false,
|
||||
progress: 0,
|
||||
error: undefined,
|
||||
log: [],
|
||||
clients: {},
|
||||
};
|
||||
}
|
||||
|
||||
connect(id: string, taskId: string, peer: PeerImpl) {
|
||||
const task = this.taskRegistry[taskId];
|
||||
if (!task) return false;
|
||||
|
||||
this.clientRegistry[id] = peer;
|
||||
this.taskRegistry[taskId].clients[id] = true; // Uniquely insert client to avoid sending duplicate traffic
|
||||
|
||||
const catchupMessage: TaskMessage = {
|
||||
id: taskId,
|
||||
success: task.success,
|
||||
error: task.error,
|
||||
log: task.log,
|
||||
progress: task.progress,
|
||||
};
|
||||
peer.send(catchupMessage);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
disconnect(id: string, taskId: string) {
|
||||
if (!this.taskRegistry[taskId]) return false;
|
||||
|
||||
delete this.taskRegistry[taskId].clients[id];
|
||||
|
||||
const allClientIds = Object.values(this.taskRegistry)
|
||||
.map((_) => Object.keys(_.clients))
|
||||
.flat();
|
||||
|
||||
if (!allClientIds.includes(id)) {
|
||||
delete this.clientRegistry[id];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export type TaskRunContext = {
|
||||
progress: (progress: number) => void;
|
||||
log: (message: string) => void;
|
||||
};
|
||||
|
||||
export interface Task {
|
||||
id: string;
|
||||
name: string;
|
||||
run: (context: TaskRunContext) => Promise<void>;
|
||||
}
|
||||
|
||||
export type TaskMessage = {
|
||||
id: string;
|
||||
success: boolean;
|
||||
progress: number;
|
||||
error: undefined | string;
|
||||
log: string[];
|
||||
};
|
||||
|
||||
export type PeerImpl = {
|
||||
send: (message: TaskMessage) => void;
|
||||
};
|
||||
|
||||
export const taskHandler = new TaskHandler();
|
||||
export default taskHandler;
|
||||
Reference in New Issue
Block a user