mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-11-05 20:20:11 +08:00
- 标签优先显示策略 initTagPriorityStrategy 提到 App.vue onMounted 统一调用, 消除组件挂载顺序导致先读内存默认 auto 的竞态窗口(AppDetailModal 移除重复调用) - InstalledAppsModal 过滤逻辑合并为单次遍历(保留 hasOrigin 双来源语义) - 统计 apm/spark/total 改为基于 searchFilteredApps 单次遍历 - canOpenDetail 补充业务含义注释与可读性变量 - 将 App.vue 重构的 9 个 composable 纳入版本管理 - 验证:vue-tsc 0 / eslint 0 / dpkg-buildpackage 打包 spark-store_5.2.1.24-test 成功
234 lines
6.9 KiB
TypeScript
234 lines
6.9 KiB
TypeScript
/**
|
||
* useDownloads —— 下载队列控制与安装触发。
|
||
*
|
||
* 从原 App.vue 原样搬移(pendingDownloadRecords / onDetailInstall /
|
||
* handleInstallCompleteForDownloadRecord / onDetailRemove / onDetailFavorite /
|
||
* pauseDownload / resumeDownload / cancelDownload / retryDownload /
|
||
* clearCompletedDownloads / showDownloadDetailModalFunc / closeDownloadDetail /
|
||
* openDownloadedApp / installCompleteCallback / watchDownloadsChange 注册),逻辑零改动。
|
||
*
|
||
* 共享状态来自 useAppState;handleInstall / handleRetry 来自 modules/processInstall;
|
||
* openFavoriteSelector 来自 useFavorites。
|
||
*/
|
||
import type {
|
||
App,
|
||
DownloadItem,
|
||
DownloadResult,
|
||
DownloadedAppRecord,
|
||
} from "../global/typedefinition";
|
||
import type { IpcRendererEvent } from "electron";
|
||
import {
|
||
downloads,
|
||
currentDownload,
|
||
showDownloadDetailModal,
|
||
currentApp,
|
||
} from "./useAppState";
|
||
import { handleInstall, handleRetry } from "../modules/processInstall";
|
||
import { watchDownloadsChange } from "../global/downloadStatus";
|
||
import { buildFavoriteAppKey, parsePackageArch } from "../modules/appIdentity";
|
||
import { openFavoriteSelector } from "./useFavorites";
|
||
import { recordDownloadedApp } from "../modules/backendApi";
|
||
import { isLoggedIn, currentUser } from "../global/authState";
|
||
|
||
interface PendingDownloadRecord {
|
||
userId: number;
|
||
appKey: string;
|
||
pkgname: string;
|
||
name: string;
|
||
category: string;
|
||
selectedOrigin: "spark" | "apm";
|
||
version: string;
|
||
packageArch: string;
|
||
}
|
||
|
||
const pendingDownloadRecords = new Map<number, PendingDownloadRecord>();
|
||
|
||
const onDetailInstall = async (app: App) => {
|
||
const initiatingUserId = currentUser.value?.id ?? null;
|
||
const download = await handleInstall(app);
|
||
if (
|
||
!download ||
|
||
initiatingUserId === null ||
|
||
!isLoggedIn.value ||
|
||
currentUser.value?.id !== initiatingUserId
|
||
) {
|
||
return;
|
||
}
|
||
|
||
pendingDownloadRecords.set(download.id, {
|
||
userId: initiatingUserId,
|
||
appKey: buildFavoriteAppKey(app),
|
||
pkgname: app.pkgname,
|
||
name: app.name,
|
||
category: app.category,
|
||
selectedOrigin: app.origin,
|
||
version: app.version,
|
||
packageArch: app.arch || parsePackageArch(app.filename),
|
||
});
|
||
};
|
||
|
||
const handleInstallCompleteForDownloadRecord = async (
|
||
_event: IpcRendererEvent,
|
||
result: DownloadResult,
|
||
) => {
|
||
const pendingRecord = pendingDownloadRecords.get(result.id);
|
||
if (!pendingRecord) return;
|
||
|
||
if (result.success) {
|
||
pendingDownloadRecords.delete(result.id);
|
||
}
|
||
|
||
if (
|
||
!result.success ||
|
||
!isLoggedIn.value ||
|
||
currentUser.value?.id !== pendingRecord.userId
|
||
) {
|
||
return;
|
||
}
|
||
|
||
const downloadRecord: Omit<DownloadedAppRecord, "id" | "downloadedAt"> = {
|
||
appKey: pendingRecord.appKey,
|
||
pkgname: pendingRecord.pkgname,
|
||
name: pendingRecord.name,
|
||
category: pendingRecord.category,
|
||
selectedOrigin: pendingRecord.selectedOrigin,
|
||
version: pendingRecord.version,
|
||
packageArch: pendingRecord.packageArch,
|
||
};
|
||
|
||
try {
|
||
await recordDownloadedApp(downloadRecord);
|
||
} catch (error: unknown) {
|
||
console.warn({ err: error }, "记录下载应用失败");
|
||
}
|
||
};
|
||
|
||
const onDetailRemove = (app: App) => {
|
||
requestUninstallRef(app);
|
||
};
|
||
|
||
const onDetailFavorite = async (app: App) => {
|
||
await openFavoriteSelector(app);
|
||
};
|
||
|
||
// TODO: 目前 APM 商店不能暂停下载
|
||
const pauseDownload = (id: DownloadItem) => {
|
||
const download = downloads.value.find((d) => d.id === id.id);
|
||
if (download && download.status === "installing") {
|
||
// 'installing' matches type definition, previously 'downloading'
|
||
download.status = "paused";
|
||
download.logs.push({
|
||
time: Date.now(),
|
||
message: "下载已暂停",
|
||
});
|
||
}
|
||
};
|
||
|
||
// TODO: 同理,暂未实现
|
||
const resumeDownload = (id: DownloadItem) => {
|
||
const download = downloads.value.find((d) => d.id === id.id);
|
||
if (download && download.status === "paused") {
|
||
download.status = "installing"; // previously 'downloading'
|
||
download.logs.push({
|
||
time: Date.now(),
|
||
message: "继续下载...",
|
||
});
|
||
// simulateDownload(download); // removed or undefined?
|
||
}
|
||
};
|
||
|
||
const cancelDownload = (id: DownloadItem) => {
|
||
const index = downloads.value.findIndex((d) => d.id === id.id);
|
||
if (index !== -1) {
|
||
const download = downloads.value[index];
|
||
// 发送到主进程取消
|
||
window.ipcRenderer.send("cancel-install", download.id);
|
||
|
||
download.status = "failed";
|
||
download.logs.push({
|
||
time: Date.now(),
|
||
message: "下载已取消",
|
||
});
|
||
// 保留在队列中以便用户可以重试或查看日志
|
||
}
|
||
};
|
||
|
||
const retryDownload = (id: DownloadItem) => {
|
||
const download = downloads.value.find((d) => d.id === id.id);
|
||
if (download && download.status === "failed") {
|
||
download.status = "queued";
|
||
download.progress = 0;
|
||
download.downloadedSize = 0;
|
||
download.logs.push({
|
||
time: Date.now(),
|
||
message: "重新开始下载...",
|
||
});
|
||
handleRetry(download);
|
||
}
|
||
};
|
||
|
||
const clearCompletedDownloads = () => {
|
||
downloads.value = downloads.value.filter((d) => d.status !== "completed");
|
||
};
|
||
|
||
const showDownloadDetailModalFunc = (download: DownloadItem) => {
|
||
currentDownload.value = download;
|
||
showDownloadDetailModal.value = true;
|
||
};
|
||
|
||
const closeDownloadDetail = () => {
|
||
showDownloadDetailModal.value = false;
|
||
currentDownload.value = null;
|
||
};
|
||
|
||
const openDownloadedApp = (pkgname: string, origin?: "spark" | "apm") => {
|
||
// const encodedPkg = encodeURIComponent(download.pkgname);
|
||
// openApmStoreUrl(`apmstore://launch?pkg=${encodedPkg}`, {
|
||
// fallbackText: `打开应用: ${download.pkgname}`
|
||
// });
|
||
window.ipcRenderer
|
||
.invoke("launch-app", { pkgname, origin })
|
||
.catch((err) => console.error("启动应用失败 (launch-app):", err));
|
||
};
|
||
|
||
const installCompleteCallback = (pkgname?: string) => {
|
||
if (currentApp.value && (!pkgname || currentApp.value.pkgname === pkgname)) {
|
||
checkAppInstalledRef(currentApp.value);
|
||
}
|
||
};
|
||
|
||
watchDownloadsChange(installCompleteCallback);
|
||
|
||
// checkAppInstalled 由 useAppDetail 持有,通过注入引用获取,避免循环依赖
|
||
let checkAppInstalledRef: (app: App) => void = () => undefined;
|
||
export const registerCheckAppInstalled = (fn: (app: App) => void) => {
|
||
checkAppInstalledRef = fn;
|
||
};
|
||
// requestUninstall 由 useInstalledApps 持有,通过注入引用获取,避免循环依赖
|
||
let requestUninstallRef: (app: App) => void = () => undefined;
|
||
export const registerRequestUninstall = (fn: (app: App) => void) => {
|
||
requestUninstallRef = fn;
|
||
};
|
||
// 登出时清空待下载记录(原 App.vue logout 中的 pendingDownloadRecords.clear())
|
||
export const clearPendingDownloadRecords = (): void => {
|
||
pendingDownloadRecords.clear();
|
||
};
|
||
|
||
// onDetailInstall 需要被 useFavorites / useAccountSync 调用,此处导出
|
||
export {
|
||
pendingDownloadRecords,
|
||
onDetailInstall,
|
||
handleInstallCompleteForDownloadRecord,
|
||
onDetailRemove,
|
||
onDetailFavorite,
|
||
pauseDownload,
|
||
resumeDownload,
|
||
cancelDownload,
|
||
retryDownload,
|
||
clearCompletedDownloads,
|
||
showDownloadDetailModalFunc,
|
||
closeDownloadDetail,
|
||
openDownloadedApp,
|
||
installCompleteCallback,
|
||
};
|