88 */
99
1010import { useReducer , useCallback , useRef } from "react" ;
11- import JSZip from "jszip" ;
1211import { saveAs } from "file-saver" ;
1312import type { DownloadState , DownloadAction , GitHubContent } from "@/types" ;
1413import { GitHub } from "@/services/github" ;
1514import { logger } from "@/utils" ;
15+ import { isAbortError } from "@/utils/network/abort" ;
1616import { requestManager } from "@/utils/request/requestManager" ;
17+ import {
18+ downloadFolderAsZip ,
19+ prepareZipOutputSink ,
20+ type FolderDownloadEntry ,
21+ type ZipOutputSink ,
22+ } from "@/utils/download/folderZipPipeline" ;
1723import { getForceServerProxy } from "@/services/github/config/ProxyForceManager" ;
1824import { useI18n } from "@/contexts/I18nContext" ;
1925
@@ -216,7 +222,7 @@ export const useDownload = (
216222 const collectFiles = useCallback (
217223 async function collectFilesInner (
218224 folderPath : string ,
219- fileList : { path : string ; url : string } [ ] ,
225+ fileList : FolderDownloadEntry [ ] ,
220226 basePath : string ,
221227 signal : AbortSignal ,
222228 ) : Promise < void > {
@@ -226,6 +232,7 @@ export const useDownload = (
226232 const contents = await requestManager . request (
227233 `download-folder-${ folderPath } ` ,
228234 ( requestSignal ) => GitHub . Content . getContents ( folderPath , requestSignal ) ,
235+ { signal } ,
229236 ) ;
230237
231238 // 检查是否已取消 (ref可在异步期间被cancelDownload修改)
@@ -258,6 +265,7 @@ export const useDownload = (
258265 fileList . push ( {
259266 path : relativePath ,
260267 url : downloadUrl ,
268+ size : item . size ,
261269 } ) ;
262270 } else {
263271 // 递归处理子文件夹 (type === 'dir')
@@ -297,95 +305,62 @@ export const useDownload = (
297305 // 创建新的AbortController
298306 abortControllerRef . current = new AbortController ( ) ;
299307 const signal = abortControllerRef . current . signal ;
308+ let outputSink : ZipOutputSink | null = null ;
309+ let zipPipelineStarted = false ;
300310
301311 try {
302- const zip = new JSZip ( ) ;
312+ outputSink = await prepareZipOutputSink ( {
313+ archiveName : `${ folderName } .zip` ,
314+ saveAsImpl : saveAs ,
315+ } ) ;
303316
304317 // 递归获取文件夹内容
305- const allFiles : { path : string ; url : string } [ ] = [ ] ;
318+ const allFiles : FolderDownloadEntry [ ] = [ ] ;
306319 await collectFiles ( path , allFiles , path , signal ) ;
307320
308321 // 检查是否已取消 (ref可在异步期间被cancelDownload修改)
309322 if ( hasBeenCancelled ( ) ) {
323+ await outputSink . abort ( ) ;
310324 logger . info ( "文件夹下载已取消" ) ;
311325 return ;
312326 }
313327
314328 dispatch ( { type : "SET_TOTAL_FILES" , count : allFiles . length } ) ;
315329 logger . info ( `需要下载的文件总数: ${ String ( allFiles . length ) } ` ) ;
316330
317- // 下载并添加到zip
318331 let processedCount = 0 ;
319- for ( const file of allFiles ) {
320- try {
321- // 检查是否已取消 (ref可在异步期间被cancelDownload修改)
322- if ( hasBeenCancelled ( ) ) {
323- logger . info ( "文件夹下载已取消" ) ;
324- return ;
325- }
326-
327- const response = await fetch ( file . url , { signal } ) ;
328-
329- if ( ! response . ok ) {
330- logger . error (
331- `文件 ${ file . path } 下载失败:` ,
332- new Error ( `下载失败: ${ String ( response . status ) } ` ) ,
333- ) ;
334- continue ;
335- }
336-
337- const blob = await response . blob ( ) ;
338- zip . file ( file . path , blob ) ;
339-
340- processedCount ++ ;
341- dispatch ( { type : "SET_PROCESSING_FILES" , count : processedCount } ) ;
332+ zipPipelineStarted = true ;
333+ await downloadFolderAsZip ( {
334+ files : allFiles ,
335+ signal,
336+ archiveName : `${ folderName } .zip` ,
337+ outputSink,
338+ onFileComplete : ( count , total ) => {
339+ processedCount = count ;
340+ dispatch ( { type : "SET_PROCESSING_FILES" , count } ) ;
342341 dispatch ( {
343342 type : "SET_FOLDER_PROGRESS" ,
344- progress : Math . round ( ( processedCount / allFiles . length ) * 100 ) ,
343+ progress : total > 0 ? Math . round ( ( count / total ) * 100 ) : 100 ,
345344 } ) ;
346- } catch ( e ) {
347- // 检查是否是取消导致的错误
348- if ( e instanceof Error && ( e . name === "AbortError" || hasBeenCancelled ( ) ) ) {
349- logger . info ( "文件夹下载已取消" ) ;
350- return ;
351- }
352- logger . error ( `文件 ${ file . path } 下载失败:` , e ) ;
353- }
354-
355- // 检查是否已取消 (ref可在异步期间被cancelDownload修改)
356- if ( hasBeenCancelled ( ) ) {
357- logger . info ( "文件夹下载已取消" ) ;
358- return ;
359- }
360- }
361-
362- // 生成zip文件
363- const zipBlob = await zip . generateAsync (
364- {
365- type : "blob" ,
366- compression : "DEFLATE" ,
367- compressionOptions : { level : 6 } ,
368345 } ,
369- ( metadata : { percent : number } ) => {
370- // 检查是否已取消 (ref可在异步期间被cancelDownload修改)
371- if ( hasBeenCancelled ( ) ) {
372- return ;
373- }
374- dispatch ( { type : "SET_FOLDER_PROGRESS" , progress : Math . round ( metadata . percent ) } ) ;
346+ onFileError : ( file , error ) => {
347+ logger . error ( `文件 ${ file . path } 下载失败:` , error ) ;
375348 } ,
376- ) ;
349+ } ) ;
377350
378- // 最后一次检查是否已取消 (ref可在异步期间被cancelDownload修改)
379- if ( hasBeenCancelled ( ) ) {
380- logger . info ( "文件夹下载已取消" ) ;
381- return ;
351+ if ( ! hasBeenCancelled ( ) ) {
352+ dispatch ( { type : "SET_PROCESSING_FILES" , count : processedCount } ) ;
353+ dispatch ( { type : "SET_FOLDER_PROGRESS" , progress : 100 } ) ;
382354 }
383355
384- saveAs ( zipBlob , `${ folderName } .zip` ) ;
385356 logger . info ( `文件夹下载完成: ${ path } ` ) ;
386357 } catch ( e : unknown ) {
387358 const error = e as Error ;
388- if ( error . name === "AbortError" || hasBeenCancelled ( ) ) {
359+ if ( outputSink !== null && ! zipPipelineStarted ) {
360+ await outputSink . abort ( error ) ;
361+ }
362+
363+ if ( isAbortError ( error ) || hasBeenCancelled ( ) ) {
389364 logger . info ( "文件夹下载已取消" ) ;
390365 } else {
391366 logger . error ( "下载文件夹失败:" , error ) ;
0 commit comments