feat(download): 支持重试下载功能并更新相关逻辑

This commit is contained in:
Elysia
2026-01-25 23:29:58 +08:00
parent 37c35c4519
commit bdf51a1037
7 changed files with 98 additions and 76 deletions

View File

@@ -21,7 +21,7 @@
- [ ] 完善项目文档
- [x] 可以展示应用列表及其详细信息
- [ ] 实现应用下载&下载列表管理
- [x] 实现应用安装
- [x] 实现应用安装&重试安装
- [ ] 实现应用卸载
- [ ] 实现应用更新
- [ ] 支持显示本地是否已经安装

View File

@@ -19,7 +19,8 @@ const tasks = new Map<number, DownloadTask>();
let idle = true; // Indicates if the installation manager is idle
// Listen for download requests from renderer process
ipcMain.on('queue-install', async (event, download) => {
ipcMain.on('queue-install', async (event, download_json) => {
const download = JSON.parse(download_json);
const { id, pkgname } = download || {};
if (!id || !pkgname) {
@@ -29,8 +30,8 @@ ipcMain.on('queue-install', async (event, download) => {
logger.info(`收到下载任务: ${id}, 软件包名称: ${pkgname}`);
// 避免重复添加同一任务
if (tasks.has(id)) {
// 避免重复添加同一任务,但允许重试下载
if (tasks.has(id) && !download.retry) {
tasks.get(id)?.webContents.send('install-log', {
id,
time: Date.now(),

View File

@@ -40,7 +40,7 @@ import DownloadQueue from './components/DownloadQueue.vue';
import DownloadDetail from './components/DownloadDetail.vue';
import { APM_STORE_ARCHITECTURE, APM_STORE_BASE_URL, currentApp } from './global/storeConfig';
import { downloads } from './global/downloadStatus';
import { handleInstall } from './js/processInstall';
import { handleInstall, handleRetry } from './modeuls/processInstall';
const logger = pino();
@@ -249,56 +249,58 @@ const escapeHtml = (s) => {
};
// 下载管理方法
const simulateDownload = (download) => {
// 模拟下载进度(实际应该调用真实的下载 API
const totalSize = Math.random() * 100 + 50; // MB
download.totalSize = totalSize * 1024 * 1024;
// 在这里保留这个方便以后参考
// const simulateDownload = (download) => {
// // 模拟下载进度(实际应该调用真实的下载 API
// const totalSize = Math.random() * 100 + 50; // MB
// download.totalSize = totalSize * 1024 * 1024;
const interval = setInterval(() => {
const downloadObj = downloads.value.find(d => d.id === download.id);
if (!downloadObj || downloadObj.status !== 'downloading') {
clearInterval(interval);
return;
}
// const interval = setInterval(() => {
// const downloadObj = downloads.value.find(d => d.id === download.id);
// if (!downloadObj || downloadObj.status !== 'downloading') {
// clearInterval(interval);
// return;
// }
// 更新进度
downloadObj.progress = Math.min(downloadObj.progress + Math.random() * 10, 100);
downloadObj.downloadedSize = (downloadObj.progress / 100) * downloadObj.totalSize;
downloadObj.speed = (Math.random() * 5 + 1) * 1024 * 1024; // 1-6 MB/s
// // 更新进度
// downloadObj.progress = Math.min(downloadObj.progress + Math.random() * 10, 100);
// downloadObj.downloadedSize = (downloadObj.progress / 100) * downloadObj.totalSize;
// downloadObj.speed = (Math.random() * 5 + 1) * 1024 * 1024; // 1-6 MB/s
const remainingBytes = downloadObj.totalSize - downloadObj.downloadedSize;
downloadObj.timeRemaining = Math.ceil(remainingBytes / downloadObj.speed);
// const remainingBytes = downloadObj.totalSize - downloadObj.downloadedSize;
// downloadObj.timeRemaining = Math.ceil(remainingBytes / downloadObj.speed);
// 添加日志
if (downloadObj.progress % 20 === 0 && downloadObj.progress > 0 && downloadObj.progress < 100) {
downloadObj.logs.push({
time: Date.now(),
message: `下载进度: ${downloadObj.progress.toFixed(0)}%`
});
}
// // 添加日志
// if (downloadObj.progress % 20 === 0 && downloadObj.progress > 0 && downloadObj.progress < 100) {
// downloadObj.logs.push({
// time: Date.now(),
// message: `下载进度: ${downloadObj.progress.toFixed(0)}%`
// });
// }
// 下载完成
if (downloadObj.progress >= 100) {
clearInterval(interval);
downloadObj.status = 'installing';
downloadObj.logs.push({
time: Date.now(),
message: '下载完成,开始安装...'
});
// // 下载完成
// if (downloadObj.progress >= 100) {
// clearInterval(interval);
// downloadObj.status = 'installing';
// downloadObj.logs.push({
// time: Date.now(),
// message: '下载完成,开始安装...'
// });
// 模拟安装
setTimeout(() => {
downloadObj.status = 'completed';
downloadObj.endTime = Date.now();
downloadObj.logs.push({
time: Date.now(),
message: '安装完成!'
});
}, 2000);
}
}, 500);
};
// // 模拟安装
// setTimeout(() => {
// downloadObj.status = 'completed';
// downloadObj.endTime = Date.now();
// downloadObj.logs.push({
// time: Date.now(),
// message: '安装完成!'
// });
// }, 2000);
// }
// }, 500);
// };
// 目前 APM 商店不能暂停下载(因为 APM 本身不支持),但保留这些方法以备将来使用
const pauseDownload = (id) => {
const download = downloads.value.find(d => d.id === id);
if (download && download.status === 'downloading') {
@@ -310,6 +312,7 @@ const pauseDownload = (id) => {
}
};
// 同理
const resumeDownload = (id) => {
const download = downloads.value.find(d => d.id === id);
if (download && download.status === 'paused') {
@@ -322,6 +325,7 @@ const resumeDownload = (id) => {
}
};
// 同理
const cancelDownload = (id) => {
const index = downloads.value.findIndex(d => d.id === id);
if (index !== -1) {
@@ -342,14 +346,14 @@ const cancelDownload = (id) => {
const retryDownload = (id) => {
const download = downloads.value.find(d => d.id === id);
if (download && download.status === 'failed') {
download.status = 'downloading';
download.status = 'queued';
download.progress = 0;
download.downloadedSize = 0;
download.logs.push({
time: Date.now(),
message: '重新开始下载...'
});
simulateDownload(download);
handleRetry(download);
}
};

View File

@@ -96,7 +96,7 @@
<!-- 操作按钮 -->
<div class="actions-section">
<button
<!-- <button
v-if="download.status === 'downloading'"
@click="pause"
class="action-btn secondary"
@@ -111,7 +111,7 @@
>
<i class="fas fa-play"></i>
继续下载
</button>
</button> -->
<button
v-if="download.status === 'failed'"
@click="retry"
@@ -120,14 +120,14 @@
<i class="fas fa-redo"></i>
重试下载
</button>
<button
<!-- <button
v-if="download.status !== 'completed'"
@click="cancel"
class="action-btn danger"
>
<i class="fas fa-times"></i>
取消下载
</button>
</button> -->
<button
v-if="download.status === 'completed'"
@click="openApp"
@@ -168,15 +168,15 @@ const handleOverlayClick = () => {
};
const pause = () => {
emit('pause', props.download.id);
// emit('pause', props.download.id);
};
const resume = () => {
emit('resume', props.download.id);
// emit('resume', props.download.id);
};
const cancel = () => {
emit('cancel', props.download.id);
//emit('cancel', props.download.id);
};
const retry = () => {
@@ -523,9 +523,11 @@ const copyLogs = () => {
display: flex;
gap: 12px;
flex-wrap: wrap;
align-items: center;
justify-content: center;
}
.action-btn {
/* .action-btn {
flex: 1;
min-width: 140px;
padding: 12px 20px;
@@ -539,9 +541,9 @@ const copyLogs = () => {
justify-content: center;
gap: 8px;
transition: all 0.2s;
}
} */
.action-btn.primary {
/* .action-btn.primary {
background: var(--primary);
color: white;
}
@@ -554,15 +556,23 @@ const copyLogs = () => {
.action-btn.secondary {
background: var(--glass);
color: var(--text);
}
} */
.action-btn.secondary:hover {
background: var(--border);
.action-btn.primary {
background: rgba(255, 59, 48, 0.1);
color: #FF3B30;
min-width: 140px;
align-items: center;
justify-content: center;
}
.action-btn.danger {
background: rgba(255, 59, 48, 0.1);
color: #FF3B30;
width: fill-available;
min-width: 140px;
align-items: center;
justify-content: center;
}
.action-btn.danger:hover {

View File

@@ -38,6 +38,7 @@
<div class="download-info">
<div class="download-name">{{ download.name }}</div>
<div class="download-status-text">
<!-- downloading 这部分APM用不到留给后续的Spark Store -->
<span v-if="download.status === 'downloading'">
下载中 {{ download.progress }}%
</span>
@@ -62,7 +63,7 @@
</div>
</div>
<div class="download-actions">
<button
<!-- <button
v-if="download.status === 'downloading'"
@click.stop="pauseDownload(download.id)"
class="action-icon"
@@ -77,7 +78,7 @@
title="继续"
>
<i class="fas fa-play"></i>
</button>
</button> -->
<button
v-if="download.status === 'failed'"
@click.stop="retryDownload(download.id)"
@@ -86,13 +87,13 @@
>
<i class="fas fa-redo"></i>
</button>
<button
<!-- <button
@click.stop="cancelDownload(download.id)"
class="action-icon"
title="取消"
>
<i class="fas fa-times"></i>
</button>
</button> -->
</div>
</div>
</div>
@@ -133,15 +134,15 @@ const toggleExpand = () => {
};
const pauseDownload = (id) => {
emit('pause', id);
// emit('pause', id);
};
const resumeDownload = (id) => {
emit('resume', id);
// emit('resume', id);
};
const cancelDownload = (id) => {
emit('cancel', id);
// emit('cancel', id);
};
const retryDownload = (id) => {

View File

@@ -29,4 +29,5 @@ export interface DownloadItem {
message: string; // 日志消息
}>;
source: string; // 例如 'APM Store'
retry: boolean; // 当前是否为重试下载
}

View File

@@ -31,23 +31,28 @@ export const handleInstall = () => {
logs: [
{ time: Date.now(), message: '开始下载...' }
],
source: 'APM Store'
source: 'APM Store',
retry: false
};
downloads.value.push(download);
// 模拟下载进度(实际应该调用真实的下载 API
// simulateDownload(download);
// Send to main process to start download
window.ipcRenderer.send('queue-install', download);
window.ipcRenderer.send('queue-install', JSON.stringify(download));
const encodedPkg = encodeURIComponent(currentApp.value.Pkgname);
// const encodedPkg = encodeURIComponent(currentApp.value.Pkgname);
// openApmStoreUrl(`apmstore://install?pkg=${encodedPkg}`, {
// fallbackText: `/usr/bin/apm-installer --install ${currentApp.value.Pkgname}`
// });
};
export const handleRetry = (download_: DownloadItem) => {
if (!download_?.pkgname) return;
download_.retry = true;
// Send to main process to start download
window.ipcRenderer.send('queue-install', JSON.stringify(download_));
};
window.ipcRenderer.on('install-status', (_event, log: InstallLog) => {
const downloadObj: any = downloads.value.find(d => d.id === log.id);
downloadObj.status = log.message;