mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-05-30 01:31:06 +08:00
修复更新中心发送的下载项和普通下载故障覆盖的问题
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
import { describe, it, expect, beforeEach } from "vitest";
|
||||
import { downloads, removeDownloadItem } from "@/global/downloadStatus";
|
||||
import {
|
||||
downloads,
|
||||
getNextDownloadId,
|
||||
removeDownloadItem,
|
||||
} from "@/global/downloadStatus";
|
||||
import type { DownloadItem } from "@/global/typedefinition";
|
||||
|
||||
describe("downloadStatus", () => {
|
||||
@@ -98,5 +102,16 @@ describe("downloadStatus", () => {
|
||||
"app-3",
|
||||
]);
|
||||
});
|
||||
|
||||
it("should not reuse ids after earlier tasks are removed", () => {
|
||||
downloads.value.push(createMockDownload(1, "app-1"));
|
||||
|
||||
const secondId = getNextDownloadId();
|
||||
downloads.value.push(createMockDownload(secondId, "app-2"));
|
||||
|
||||
downloads.value = [];
|
||||
|
||||
expect(getNextDownloadId()).toBe(secondId + 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -33,4 +33,78 @@ describe("processInstall queue forwarding", () => {
|
||||
|
||||
expect(send).toHaveBeenCalledWith("queue-install", payload);
|
||||
});
|
||||
|
||||
it("allocates install ids after existing update tasks", async () => {
|
||||
const handlers = new Map<string, (...args: unknown[]) => void>();
|
||||
const send = vi.fn();
|
||||
|
||||
vi.doMock("axios", () => ({
|
||||
default: {
|
||||
create: vi.fn(() => ({
|
||||
post: vi.fn(() => Promise.resolve({ data: { ok: true } })),
|
||||
})),
|
||||
},
|
||||
}));
|
||||
|
||||
Object.assign(window.ipcRenderer, {
|
||||
on: vi.fn((channel: string, handler: (...args: unknown[]) => void) => {
|
||||
handlers.set(channel, handler);
|
||||
}),
|
||||
send,
|
||||
invoke: vi.fn(),
|
||||
});
|
||||
|
||||
window.apm_store.arch = "amd64";
|
||||
|
||||
const { downloads } = await import("@/global/downloadStatus");
|
||||
downloads.value = [
|
||||
{
|
||||
id: 4,
|
||||
name: "Spark Weather",
|
||||
pkgname: "spark-weather",
|
||||
version: "2.0.0",
|
||||
icon: "https://example.com/icon.png",
|
||||
origin: "spark",
|
||||
status: "downloading",
|
||||
progress: 0,
|
||||
downloadedSize: 0,
|
||||
totalSize: 1024,
|
||||
speed: 0,
|
||||
timeRemaining: 0,
|
||||
startTime: Date.now(),
|
||||
logs: [],
|
||||
source: "Update Center",
|
||||
retry: false,
|
||||
upgradeOnly: true,
|
||||
},
|
||||
];
|
||||
|
||||
const { handleInstall } = await import("@/modules/processInstall");
|
||||
|
||||
await handleInstall({
|
||||
name: "Spark Notes",
|
||||
pkgname: "spark-notes",
|
||||
version: "1.0.0",
|
||||
filename: "spark-notes_1.0.0_amd64.deb",
|
||||
torrent_address: "spark-notes_1.0.0_amd64.deb.torrent",
|
||||
author: "Tester",
|
||||
contributor: "Tester",
|
||||
website: "https://example.com",
|
||||
update: "2026-04-13",
|
||||
size: "10MB",
|
||||
more: "Test app",
|
||||
tags: "test",
|
||||
img_urls: [],
|
||||
icons: "https://example.com/icon.png",
|
||||
category: "office",
|
||||
origin: "spark",
|
||||
currentStatus: "not-installed",
|
||||
});
|
||||
|
||||
expect(downloads.value.map((download) => download.id)).toEqual([4, 5]);
|
||||
expect(send).toHaveBeenCalledWith(
|
||||
"queue-install",
|
||||
expect.stringContaining('"id":5'),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { createUpdateCenterService } from "../../../../electron/main/backend/update-center/service";
|
||||
|
||||
const electronMock = vi.hoisted(() => ({
|
||||
getAllWindows: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("electron", () => ({
|
||||
BrowserWindow: {
|
||||
getAllWindows: electronMock.getAllWindows,
|
||||
},
|
||||
}));
|
||||
|
||||
describe("update-center service id forwarding", () => {
|
||||
beforeEach(() => {
|
||||
electronMock.getAllWindows.mockReset();
|
||||
});
|
||||
|
||||
it("forwards renderer-assigned ids into queue-install payloads", async () => {
|
||||
const send = vi.fn();
|
||||
electronMock.getAllWindows.mockReturnValue([{ webContents: { send } }]);
|
||||
|
||||
const service = createUpdateCenterService({
|
||||
loadItems: async () => [
|
||||
{
|
||||
pkgname: "spark-weather",
|
||||
source: "aptss",
|
||||
currentVersion: "1.0.0",
|
||||
nextVersion: "2.0.0",
|
||||
fileName: "spark-weather.deb",
|
||||
downloadUrl: "https://example.com/spark-weather.deb",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await service.refresh();
|
||||
await service.start([{ taskKey: "aptss:spark-weather", id: 42 }]);
|
||||
|
||||
expect(send).toHaveBeenCalledWith(
|
||||
"queue-install",
|
||||
JSON.stringify({
|
||||
id: 42,
|
||||
pkgname: "spark-weather",
|
||||
metalinkUrl: "https://example.com/spark-weather.deb.metalink",
|
||||
filename: "spark-weather.deb",
|
||||
upgradeOnly: true,
|
||||
origin: "spark",
|
||||
retry: false,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -96,7 +96,12 @@ describe("updateCenter store", () => {
|
||||
store.toggleSelection("apm:spark-clock");
|
||||
await store.startSelected();
|
||||
|
||||
expect(start).toHaveBeenCalledWith(["aptss:spark-weather"]);
|
||||
expect(start).toHaveBeenCalledWith([
|
||||
{
|
||||
taskKey: "aptss:spark-weather",
|
||||
id: downloads.value[0]?.id,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("uses remoteIcon when adding update tasks to the download queue", async () => {
|
||||
@@ -127,6 +132,45 @@ describe("updateCenter store", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("assigns update-center download ids from a separate range", async () => {
|
||||
downloads.value = [
|
||||
{
|
||||
id: 5,
|
||||
name: "Spark Notes",
|
||||
pkgname: "spark-notes",
|
||||
version: "1.0.0",
|
||||
icon: "https://example.com/icons/spark-notes.png",
|
||||
origin: "spark",
|
||||
status: "queued",
|
||||
progress: 0,
|
||||
downloadedSize: 0,
|
||||
totalSize: 1024,
|
||||
speed: 0,
|
||||
timeRemaining: 0,
|
||||
startTime: Date.now(),
|
||||
logs: [],
|
||||
source: "APM Store",
|
||||
retry: false,
|
||||
},
|
||||
];
|
||||
const snapshot = createSnapshot();
|
||||
open.mockResolvedValue(snapshot);
|
||||
const store = createUpdateCenterStore();
|
||||
|
||||
await store.open();
|
||||
store.toggleSelection("aptss:spark-weather");
|
||||
await store.startSelected();
|
||||
|
||||
expect(downloads.value).toHaveLength(2);
|
||||
expect(downloads.value[1]?.id).toBeLessThan(0);
|
||||
expect(start).toHaveBeenCalledWith([
|
||||
{
|
||||
taskKey: "aptss:spark-weather",
|
||||
id: downloads.value[1]?.id,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("blocks close requests while the snapshot reports running tasks", () => {
|
||||
const store = createUpdateCenterStore();
|
||||
store.isOpen.value = true;
|
||||
|
||||
@@ -191,7 +191,8 @@ describe("update-center task runner", () => {
|
||||
{
|
||||
id: task.id,
|
||||
status: "failed",
|
||||
error: "Update task for spark-player requires download metadata (URL and filename)",
|
||||
error:
|
||||
"Update task for spark-player requires download metadata (URL and filename)",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -49,7 +49,9 @@
|
||||
:checked="allSelected"
|
||||
@change="$emit('toggle-select-all')"
|
||||
/>
|
||||
<span class="text-sm font-medium text-slate-700 dark:text-slate-200">全选</span>
|
||||
<span class="text-sm font-medium text-slate-700 dark:text-slate-200"
|
||||
>全选</span
|
||||
>
|
||||
</label>
|
||||
<span class="text-sm text-slate-400 dark:text-slate-500">
|
||||
已选 {{ selectedCount }} 项
|
||||
@@ -93,7 +95,8 @@ watch(
|
||||
[() => props.someSelected, () => props.allSelected],
|
||||
() => {
|
||||
if (selectAllRef.value) {
|
||||
selectAllRef.value.indeterminate = props.someSelected && !props.allSelected;
|
||||
selectAllRef.value.indeterminate =
|
||||
props.someSelected && !props.allSelected;
|
||||
}
|
||||
},
|
||||
{ flush: "post" },
|
||||
|
||||
@@ -3,6 +3,34 @@ import type { DownloadItem, DownloadItemStatus } from "./typedefinition";
|
||||
|
||||
export const downloads = ref<DownloadItem[]>([]);
|
||||
|
||||
let nextDownloadId = 1;
|
||||
|
||||
export function getNextDownloadId(): number {
|
||||
if (downloads.value.length > 0) {
|
||||
nextDownloadId = Math.max(
|
||||
nextDownloadId,
|
||||
Math.max(...downloads.value.map((item) => item.id)) + 1,
|
||||
);
|
||||
}
|
||||
|
||||
const downloadId = nextDownloadId;
|
||||
nextDownloadId += 1;
|
||||
|
||||
return downloadId;
|
||||
}
|
||||
|
||||
export function getNextUpdateDownloadId(): number {
|
||||
const negativeIds = downloads.value
|
||||
.map((item) => item.id)
|
||||
.filter((id) => id < 0);
|
||||
|
||||
if (negativeIds.length === 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return Math.min(...negativeIds) - 1;
|
||||
}
|
||||
|
||||
export function removeDownloadItem(pkgname: string) {
|
||||
const list = downloads.value;
|
||||
for (let i = list.length - 1; i >= 0; i -= 1) {
|
||||
|
||||
@@ -165,6 +165,11 @@ export interface UpdateCenterTaskState {
|
||||
errorMessage: string;
|
||||
}
|
||||
|
||||
export interface UpdateCenterStartTask {
|
||||
taskKey: string;
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface UpdateCenterSnapshot {
|
||||
items: UpdateCenterItem[];
|
||||
tasks: UpdateCenterTaskState[];
|
||||
@@ -183,7 +188,7 @@ export interface UpdateCenterBridge {
|
||||
packageName: string;
|
||||
newVersion: string;
|
||||
}) => Promise<void>;
|
||||
start: (taskKeys: string[]) => Promise<void>;
|
||||
start: (tasks: UpdateCenterStartTask[]) => Promise<void>;
|
||||
cancel: (taskKey: string) => Promise<void>;
|
||||
getState: () => Promise<UpdateCenterSnapshot>;
|
||||
onState: (listener: (snapshot: UpdateCenterSnapshot) => void) => void;
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
currentAppApmInstalled,
|
||||
} from "../global/storeConfig";
|
||||
import { APM_STORE_BASE_URL } from "../global/storeConfig";
|
||||
import { downloads } from "../global/downloadStatus";
|
||||
import { downloads, getNextDownloadId } from "../global/downloadStatus";
|
||||
|
||||
import {
|
||||
InstallLog,
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
} from "../global/typedefinition";
|
||||
import axios from "axios";
|
||||
|
||||
let downloadIdCounter = 0;
|
||||
const logger = pino({ name: "processInstall.ts" });
|
||||
|
||||
export const handleInstall = async (appObj?: App) => {
|
||||
@@ -51,14 +50,14 @@ export const handleInstall = async (appObj?: App) => {
|
||||
return;
|
||||
}
|
||||
|
||||
downloadIdCounter += 1;
|
||||
// 创建下载任务
|
||||
const arch = window.apm_store.arch || "amd64";
|
||||
const finalArch =
|
||||
targetApp.origin === "spark" ? `${arch}-store` : `${arch}-apm`;
|
||||
const downloadId = getNextDownloadId();
|
||||
|
||||
const download: DownloadItem = {
|
||||
id: downloadIdCounter,
|
||||
id: downloadId,
|
||||
name: targetApp.name,
|
||||
pkgname: targetApp.pkgname,
|
||||
version: targetApp.version,
|
||||
@@ -140,12 +139,12 @@ export const handleUpgrade = async (app: App) => {
|
||||
return;
|
||||
}
|
||||
|
||||
downloadIdCounter += 1;
|
||||
const arch = window.apm_store.arch || "amd64";
|
||||
const finalArch = app.origin === "spark" ? `${arch}-store` : `${arch}-apm`;
|
||||
const downloadId = getNextDownloadId();
|
||||
|
||||
const download: DownloadItem = {
|
||||
id: downloadIdCounter,
|
||||
id: downloadId,
|
||||
name: app.name,
|
||||
pkgname: app.pkgname,
|
||||
version: app.version,
|
||||
|
||||
+23
-12
@@ -4,8 +4,9 @@ import type {
|
||||
UpdateCenterItem,
|
||||
UpdateCenterSnapshot,
|
||||
DownloadItem,
|
||||
UpdateCenterStartTask,
|
||||
} from "@/global/typedefinition";
|
||||
import { downloads } from "@/global/downloadStatus";
|
||||
import { downloads, getNextUpdateDownloadId } from "@/global/downloadStatus";
|
||||
import { APM_STORE_BASE_URL } from "@/global/storeConfig";
|
||||
|
||||
const EMPTY_SNAPSHOT: UpdateCenterSnapshot = {
|
||||
@@ -88,12 +89,18 @@ export const createUpdateCenterStore = (): UpdateCenterStore => {
|
||||
|
||||
const allSelected = computed(() => {
|
||||
const selectable = selectableItems.value;
|
||||
return selectable.length > 0 && selectable.every((item) => selectedTaskKeys.value.has(item.taskKey));
|
||||
return (
|
||||
selectable.length > 0 &&
|
||||
selectable.every((item) => selectedTaskKeys.value.has(item.taskKey))
|
||||
);
|
||||
});
|
||||
|
||||
const someSelected = computed(() => {
|
||||
const selectable = selectableItems.value;
|
||||
return selectable.length > 0 && selectable.some((item) => selectedTaskKeys.value.has(item.taskKey));
|
||||
return (
|
||||
selectable.length > 0 &&
|
||||
selectable.some((item) => selectedTaskKeys.value.has(item.taskKey))
|
||||
);
|
||||
});
|
||||
|
||||
const handleState = (nextSnapshot: UpdateCenterSnapshot): void => {
|
||||
@@ -173,18 +180,13 @@ export const createUpdateCenterStore = (): UpdateCenterStore => {
|
||||
|
||||
const startSelected = async (): Promise<void> => {
|
||||
const selectedItems = getSelectedItems();
|
||||
const taskKeys = selectedItems.map((item) => item.taskKey);
|
||||
|
||||
if (taskKeys.length === 0) {
|
||||
if (selectedItems.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 在前端创建下载项,这样用户能在下载列表中看到更新任务
|
||||
const arch = window.apm_store.arch || "amd64";
|
||||
let downloadIdCounter =
|
||||
downloads.value.length > 0
|
||||
? Math.max(...downloads.value.map((d) => d.id)) + 1
|
||||
: 1;
|
||||
const startTasks: UpdateCenterStartTask[] = [];
|
||||
|
||||
selectedItems.forEach((item) => {
|
||||
// 检查任务是否已存在
|
||||
@@ -200,8 +202,9 @@ export const createUpdateCenterStore = (): UpdateCenterStore => {
|
||||
const icon =
|
||||
item.remoteIcon ||
|
||||
`${APM_STORE_BASE_URL}/${finalArch}/unknown/${item.packageName}/icon.png`;
|
||||
const downloadId = getNextUpdateDownloadId();
|
||||
const download: DownloadItem = {
|
||||
id: downloadIdCounter++,
|
||||
id: downloadId,
|
||||
name: item.displayName,
|
||||
pkgname: item.packageName,
|
||||
version: item.newVersion,
|
||||
@@ -224,10 +227,18 @@ export const createUpdateCenterStore = (): UpdateCenterStore => {
|
||||
: undefined,
|
||||
};
|
||||
downloads.value.push(download);
|
||||
startTasks.push({
|
||||
taskKey: item.taskKey,
|
||||
id: downloadId,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
await window.updateCenter.start(taskKeys);
|
||||
if (startTasks.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
await window.updateCenter.start(startTasks);
|
||||
};
|
||||
|
||||
const requestClose = (): void => {
|
||||
|
||||
Reference in New Issue
Block a user