Skip to content

Commit cbb63a0

Browse files
committed
🎈 perf: 优化下载逻辑
1 parent a421ea5 commit cbb63a0

4 files changed

Lines changed: 91 additions & 122 deletions

File tree

electron/main/ipc/ipc-file.ts

Lines changed: 90 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
1-
import { type DownloadItem, app, BrowserWindow, dialog, ipcMain, shell } from "electron";
1+
import { app, BrowserWindow, dialog, ipcMain, shell } from "electron";
22
import { basename, dirname, extname, isAbsolute, join, relative, resolve } from "path";
33
import { access, mkdir, readdir, readFile, stat, unlink, writeFile } from "fs/promises";
44
import { parseFile } from "music-metadata";
55
import { getFileID, getFileMD5, metaDataLyricsArrayToLrc } from "../utils/helper";
66
import { File, Picture, Id3v2Settings, TagTypes } from "node-taglib-sharp";
77
import { ipcLog } from "../logger";
8-
import { download } from "electron-dl";
8+
import { createWriteStream } from "fs";
9+
import { pipeline } from "stream/promises";
910
import { Options as GlobOptions } from "fast-glob/out/settings";
1011
import { LocalMusicService } from "../services/LocalMusicService";
1112
import { useStore } from "../store";
1213
import FastGlob from "fast-glob";
1314
import pLimit from "p-limit";
15+
import got from "got";
1416

15-
// 下载项
16-
const downloadItems = new Map<number, DownloadItem>();
17+
// 下载项 (存储 AbortController)
18+
const downloadItems = new Map<number, AbortController>();
1719

1820
/**
1921
* 文件相关 IPC
@@ -517,26 +519,59 @@ const initFileIpc = (): void => {
517519
}
518520

519521
// 下载文件
520-
let songDownload: DownloadItem;
522+
const abortController = new AbortController();
523+
if (songData?.id) {
524+
downloadItems.set(songData.id, abortController);
525+
}
526+
527+
const finalFilePath = join(downloadPath, `${fileName}.${fileType}`);
528+
const fileStream = createWriteStream(finalFilePath);
529+
521530
try {
522-
songDownload = await download(win, url, {
523-
directory: downloadPath,
524-
filename: `${fileName}.${fileType}`,
525-
showProgressBar: false,
526-
onProgress: (progress) => {
527-
win.webContents.send("download-progress", { ...progress, id: songData?.id });
528-
},
529-
onStarted: (item) => {
530-
if (songData?.id) {
531-
downloadItems.set(songData.id, item);
532-
}
533-
},
531+
const downloadStream = got.stream(url, {
532+
signal: abortController.signal,
533+
retry: { limit: 0 }, // 禁止自动重试,防止进度条跳变
534534
});
535-
} catch (error: unknown) {
536-
if (error instanceof Error && error.message === "The download was cancelled") {
537-
return { status: "cancelled", message: "下载已取消" };
538-
}
539-
throw error;
535+
536+
let lastProgressTime = 0;
537+
let lastPercent = 0;
538+
539+
downloadStream.on("downloadProgress", (progress) => {
540+
const now = Date.now();
541+
// 限制发送频率:每秒或进度变化超过 5%
542+
if (now - lastProgressTime > 1000 || progress.percent - lastPercent >= 0.05) {
543+
win.webContents.send("download-progress", {
544+
id: songData?.id,
545+
percent: progress.percent,
546+
transferredBytes: progress.transferred,
547+
totalBytes: progress.total,
548+
});
549+
lastProgressTime = now;
550+
lastPercent = progress.percent;
551+
}
552+
});
553+
554+
await pipeline(downloadStream, fileStream);
555+
556+
// 发送 100% 进度
557+
win.webContents.send("download-progress", {
558+
id: songData?.id,
559+
percent: 1,
560+
transferredBytes: 0,
561+
totalBytes: 0,
562+
});
563+
} catch (error: any) {
564+
// 删除未完成的文件
565+
try {
566+
await unlink(finalFilePath);
567+
} catch {
568+
// 忽略错误
569+
}
570+
571+
if (error.name === "AbortError" || error.code === "ABORT_ERR") {
572+
return { status: "cancelled", message: "下载已取消" };
573+
}
574+
throw error;
540575
} finally {
541576
if (songData?.id) {
542577
downloadItems.delete(songData.id);
@@ -546,37 +581,47 @@ const initFileIpc = (): void => {
546581
if (!downloadMeta || !songData?.cover) return { status: "success" };
547582

548583
// 验证文件是否存在
549-
const savedPath = songDownload.getSavePath();
550584
try {
551-
await access(savedPath);
552-
} catch (e) {
585+
await access(finalFilePath);
586+
} catch {
553587
// 等待一小段时间再次检查(解决某些情况下文件系统延迟)
554588
await new Promise((resolve) => setTimeout(resolve, 500));
555589
try {
556-
await access(savedPath);
590+
await access(finalFilePath);
557591
} catch {
558-
throw new Error(`File not found at ${savedPath}`);
592+
throw new Error(`File not found at ${finalFilePath}`);
559593
}
560594
}
561595

562596
// 下载封面
563597
const coverUrl = songData?.coverSize?.l || songData.cover;
564-
const coverDownload = await download(win, coverUrl, {
565-
directory: downloadPath,
566-
filename: `${fileName}.jpg`,
567-
showProgressBar: false,
568-
});
598+
let coverPath = "";
599+
try {
600+
const coverBuffer = await got(coverUrl).buffer();
601+
coverPath = join(downloadPath, `${fileName}.jpg`);
602+
await writeFile(coverPath, coverBuffer);
603+
} catch (e) {
604+
console.error("Cover download failed", e);
605+
}
606+
569607
// 读取歌曲文件
570-
let songFile = File.createFromPath(savedPath);
608+
let songFile = File.createFromPath(finalFilePath);
571609
// 清除原有标签,防止脏数据(如模拟播放下载时的乱码歌词)
572610
songFile.removeTags(TagTypes.AllTags);
573611
songFile.save();
574612
songFile.dispose();
575613

576614
// 重新读取文件以写入新标签
577-
songFile = File.createFromPath(songDownload.getSavePath());
615+
songFile = File.createFromPath(finalFilePath);
578616
// 生成图片信息
579-
const songCover = Picture.fromPath(coverDownload.getSavePath());
617+
let songCover: Picture | null = null;
618+
if (coverPath) {
619+
try {
620+
songCover = Picture.fromPath(coverPath);
621+
} catch {
622+
// 忽略错误
623+
}
624+
}
580625

581626
// 保存修改后的元数据
582627
Id3v2Settings.forceDefaultVersion = true;
@@ -597,7 +642,13 @@ const initFileIpc = (): void => {
597642
await writeFile(lrcPath, lyric, "utf-8");
598643
}
599644
// 是否删除封面
600-
if (!saveMetaFile || !downloadCover) await unlink(coverDownload.getSavePath());
645+
if (coverPath && (!saveMetaFile || !downloadCover)) {
646+
try {
647+
await unlink(coverPath);
648+
} catch {
649+
// 忽略错误
650+
}
651+
}
601652
return { status: "success" };
602653
} catch (error) {
603654
ipcLog.error("❌ Error downloading file:", error);
@@ -611,9 +662,9 @@ const initFileIpc = (): void => {
611662

612663
// 取消下载
613664
ipcMain.handle("cancel-download", async (_, songId: number) => {
614-
const item = downloadItems.get(songId);
615-
if (item) {
616-
item.cancel();
665+
const controller = downloadItems.get(songId);
666+
if (controller) {
667+
controller.abort();
617668
downloadItems.delete(songId);
618669
return true;
619670
}

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@
5757
"axios-retry": "^4.5.0",
5858
"change-case": "^5.4.4",
5959
"dayjs": "^1.11.19",
60-
"electron-dl": "^4.0.0",
6160
"electron-store": "^11.0.2",
6261
"electron-updater": "^6.6.2",
6362
"file-saver": "^2.0.5",

0 commit comments

Comments
 (0)