Files
spark-store/electron/main/backend/update-center/queue.ts
momen 0b17ada45a feat(update-center): 实现集中式软件更新中心功能
新增更新中心模块,支持管理 APM 和传统 deb 软件更新任务
- 添加更新任务队列管理、状态跟踪和日志记录功能
- 实现更新项忽略配置持久化存储
- 新增更新确认对话框和迁移提示
- 优化主窗口关闭时的任务保护机制
- 添加单元测试覆盖核心逻辑
2026-04-09 08:19:51 +08:00

159 lines
3.8 KiB
TypeScript

import type { UpdateCenterItem } from "./types";
export type UpdateCenterTaskStatus =
| "queued"
| "downloading"
| "installing"
| "completed"
| "failed"
| "cancelled";
export interface UpdateCenterTaskLog {
time: number;
message: string;
}
export interface UpdateCenterTask {
id: number;
pkgname: string;
item: UpdateCenterItem;
status: UpdateCenterTaskStatus;
progress: number;
logs: UpdateCenterTaskLog[];
error?: string;
}
export interface UpdateCenterQueueSnapshot {
items: UpdateCenterItem[];
tasks: UpdateCenterTask[];
warnings: string[];
hasRunningTasks: boolean;
}
export interface UpdateCenterQueue {
setItems: (items: UpdateCenterItem[]) => void;
startRefresh: () => void;
finishRefresh: (warnings?: string[]) => void;
enqueueItem: (item: UpdateCenterItem) => UpdateCenterTask;
markActiveTask: (
taskId: number,
status: Extract<UpdateCenterTaskStatus, "downloading" | "installing">,
) => void;
updateTaskProgress: (taskId: number, progress: number) => void;
appendTaskLog: (taskId: number, message: string, time?: number) => void;
finishTask: (
taskId: number,
status: Extract<
UpdateCenterTaskStatus,
"completed" | "failed" | "cancelled"
>,
error?: string,
) => void;
getNextQueuedTask: () => UpdateCenterTask | undefined;
getSnapshot: () => UpdateCenterQueueSnapshot;
}
const clampProgress = (progress: number): number => {
if (!Number.isFinite(progress)) {
return 0;
}
return Math.max(0, Math.min(100, Math.round(progress)));
};
const createSnapshot = (
items: UpdateCenterItem[],
tasks: UpdateCenterTask[],
warnings: string[],
refreshing: boolean,
): UpdateCenterQueueSnapshot => ({
items: items.map((item) => ({ ...item })),
tasks: tasks.map((task) => ({
...task,
item: { ...task.item },
logs: task.logs.map((log) => ({ ...log })),
})),
warnings: [...warnings],
hasRunningTasks:
refreshing ||
tasks.some((task) =>
["queued", "downloading", "installing"].includes(task.status),
),
});
export const createUpdateCenterQueue = (): UpdateCenterQueue => {
let items: UpdateCenterItem[] = [];
let tasks: UpdateCenterTask[] = [];
let warnings: string[] = [];
let refreshing = false;
let nextTaskId = 1;
const getTask = (taskId: number): UpdateCenterTask | undefined =>
tasks.find((task) => task.id === taskId);
return {
setItems: (nextItems) => {
items = nextItems.map((item) => ({ ...item }));
},
startRefresh: () => {
refreshing = true;
},
finishRefresh: (nextWarnings = []) => {
refreshing = false;
warnings = [...nextWarnings];
},
enqueueItem: (item) => {
const task: UpdateCenterTask = {
id: nextTaskId,
pkgname: item.pkgname,
item: { ...item },
status: "queued",
progress: 0,
logs: [],
};
nextTaskId += 1;
tasks = [...tasks, task];
return task;
},
markActiveTask: (taskId, status) => {
const task = getTask(taskId);
if (!task) {
return;
}
task.status = status;
},
updateTaskProgress: (taskId, progress) => {
const task = getTask(taskId);
if (!task) {
return;
}
task.progress = clampProgress(progress);
},
appendTaskLog: (taskId, message, time = Date.now()) => {
const task = getTask(taskId);
if (!task) {
return;
}
task.logs = [...task.logs, { time, message }];
},
finishTask: (taskId, status, error) => {
const task = getTask(taskId);
if (!task) {
return;
}
task.status = status;
task.error = error;
if (status === "completed") {
task.progress = 100;
}
},
getNextQueuedTask: () => tasks.find((task) => task.status === "queued"),
getSnapshot: () => createSnapshot(items, tasks, warnings, refreshing),
};
};