1- import { type DownloadItem , app , BrowserWindow , dialog , ipcMain , shell } from "electron" ;
1+ import { app , BrowserWindow , dialog , ipcMain , shell } from "electron" ;
22import { basename , dirname , extname , isAbsolute , join , relative , resolve } from "path" ;
33import { access , mkdir , readdir , readFile , stat , unlink , writeFile } from "fs/promises" ;
44import { parseFile } from "music-metadata" ;
55import { getFileID , getFileMD5 , metaDataLyricsArrayToLrc } from "../utils/helper" ;
66import { File , Picture , Id3v2Settings , TagTypes } from "node-taglib-sharp" ;
77import { ipcLog } from "../logger" ;
8- import { download } from "electron-dl" ;
8+ import { createWriteStream } from "fs" ;
9+ import { pipeline } from "stream/promises" ;
910import { Options as GlobOptions } from "fast-glob/out/settings" ;
1011import { LocalMusicService } from "../services/LocalMusicService" ;
1112import { useStore } from "../store" ;
1213import FastGlob from "fast-glob" ;
1314import 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 }
0 commit comments