@@ -5,6 +5,7 @@ import { onMounted } from 'vue';
55import { Download , FileText , HardDrive } from ' lucide-vue-next' ;
66import { downloadFile } from ' @/utils/requests' ;
77import { processDownloadWithConcurrencyLimit } from ' @/utils/asyncPool' ;
8+ import { receiveViaWebRtc } from ' @/utils/webrtc' ;
89
910const { Title, Text } = Typography ;
1011
@@ -26,7 +27,7 @@ const formatBytes = (bytes: number, decimals = 2) => {
2627const handleGetFile = async () => {
2728 isDownloading .value = true ;
2829 downloadProgress .value = 0 ;
29-
30+
3031 let start = 0 ;
3132 let fileData: Map <number , Uint8Array > = new Map ();
3233 let downloadedBytes = 0 ;
@@ -35,117 +36,158 @@ const handleGetFile = async () => {
3536 const pathParts = window .location .pathname .split (' /' );
3637 const fileId = pathParts [1 ]; // Assumes URL format is /{id}/file
3738
38- // Create an array to hold all chunk download promises
39- const downloadPromises: Array <() => Promise <any >> = [];
39+ const downloadViaHttp = async () => {
40+ // Create an array to hold all chunk download promises
41+ const downloadPromises: Array <() => Promise <any >> = [];
4042
41- // Add chunks to download (in 1MB chunks)
42- while (start < fileSize .value ) {
43- const chunkEnd = Math .min (start + 1024 * 1024 , fileSize .value ) - 1 ;
44- const currentStart = start ; // 保存当前start值的快照
43+ // Add chunks to download (in 1MB chunks)
44+ while (start < fileSize .value ) {
45+ const chunkEnd = Math .min (start + 1024 * 1024 , fileSize .value ) - 1 ;
46+ const currentStart = start ; // 保存当前start值的快照
4547
46- // Create a function that returns a promise for this chunk download
47- // Pass fileId and the byte range start, but not fileName since it's not needed for the request
48- downloadPromises .push (() => downloadFile (fileId , currentStart , fileName ));
48+ // Create a function that returns a promise for this chunk download
49+ // Pass fileId and the byte range start, but not fileName since it's not needed for the request
50+ downloadPromises .push (() => downloadFile (fileId , currentStart , fileName ));
4951
50- if (chunkEnd === fileSize .value - 1 ) break ;
51- start += 1024 * 1024 ;
52- }
52+ if (chunkEnd === fileSize .value - 1 ) break ;
53+ start += 1024 * 1024 ;
54+ }
5355
54- // Process downloads with a concurrency limit of 4
55- try {
56- await processDownloadWithConcurrencyLimit (downloadPromises , 4 , async (rangeMatch : RegExpMatchArray , response : Response ) => {
56+ // Process downloads with a concurrency limit of 4
57+ try {
58+ await processDownloadWithConcurrencyLimit (downloadPromises , 4 , async (rangeMatch : RegExpMatchArray , response : Response ) => {
5759 const rangeStart = parseInt (rangeMatch [1 ]);
5860
59- // Get the file data
60- const data = await response .arrayBuffer ();
61- const chunk = new Uint8Array (data );
62- fileData .set (rangeStart , chunk );
61+ // Get the file data
62+ const data = await response .arrayBuffer ();
63+ const chunk = new Uint8Array (data );
64+ fileData .set (rangeStart , chunk );
6365
64- // Update progress - 累加当前chunk的大小而不是重新计算所有chunks
65- downloadedBytes += chunk .length ;
66+ // Update progress - 累加当前chunk的大小而不是重新计算所有chunks
67+ downloadedBytes += chunk .length ;
6668
67- // Only update progress if we have a valid totalSize
68- if (fileSize .value > 0 ) {
69- downloadProgress .value = Math .round ((downloadedBytes / fileSize .value ) * 100 );
70- }
71- });
72- } catch (error ) {
73- message .error (' 下载过程中发生错误: ' + (error instanceof Error ? error .message : ' 未知错误' ));
74- isDownloading .value = false ;
75- return ;
76- }
69+ // Only update progress if we have a valid totalSize
70+ if (fileSize .value > 0 ) {
71+ downloadProgress .value = Math .round ((downloadedBytes / fileSize .value ) * 100 );
72+ }
73+ });
74+ } catch (error ) {
75+ message .error (' 下载过程中发生错误: ' + (error instanceof Error ? error .message : ' 未知错误' ));
76+ isDownloading .value = false ;
77+ return ;
78+ }
7779
78- await new Promise <void >(resolve => { setTimeout (resolve , 500 )});
80+ await new Promise <void >(resolve => { setTimeout (resolve , 500 )});
7981
80- // Combine all chunks and save the file
81- if (fileData .size > 0 ) {
82- try {
83- // Combine all Uint8Array chunks into a single Uint8Array
84- const combinedData = new Uint8Array (fileSize .value );
85- let offset = 0 ;
86- const sortedChunks = Array .from (fileData .entries ()).sort ((a , b ) => a [0 ] - b [0 ]);
82+ // Combine all chunks and save the file
83+ if (fileData .size > 0 ) {
84+ try {
85+ // Combine all Uint8Array chunks into a single Uint8Array
86+ const combinedData = new Uint8Array (fileSize .value );
87+ let offset = 0 ;
88+ const sortedChunks = Array .from (fileData .entries ()).sort ((a , b ) => a [0 ] - b [0 ]);
8789
88- for (const [start, chunk] of sortedChunks ) {
89- if (start !== offset ){
90+ for (const [start, chunk] of sortedChunks ) {
91+ if (start !== offset ) {
9092 message .error (` 文件 ${fileName .value } 中有缺失的块,请重新上传 ` );
9193 return ;
94+ }
95+ combinedData .set (chunk , offset );
96+ offset += chunk .length ;
9297 }
93- combinedData .set (chunk , offset );
94- offset += chunk .length ;
95- }
96-
97- // Create a Blob from the combined data
98- const blob = new Blob ([combinedData ], { type: ' application/octet-stream' });
99-
100- // Create a download link and trigger the download
101- const url = URL .createObjectURL (blob );
102- const a = document .createElement (' a' );
103- a .href = url ;
104- a .download = fileName .value || ' downloaded_file' ;
105- document .body .appendChild (a );
106- a .click ();
107-
108- // Clean up
109- document .body .removeChild (a );
110- URL .revokeObjectURL (url );
11198
112- message .success (' 文件下载完成!' );
113-
114- // Send download completion signal to server
115- try {
116- const pathParts = window .location .pathname .split (' /' );
117- const fileId = pathParts [1 ]; // Assumes URL format is /{id}/file
118-
119- const response = await fetch (` /api/${fileId }/done ` , {
120- method: ' PUT' ,
121- headers: {
122- ' Content-Type' : ' application/json' ,
123- },
124- body: JSON .stringify ({})
125- });
126-
127- if (response .ok ) {
128- // Download completion signal sent successfully
129- } else {
99+ // Create a Blob from the combined data
100+ const blob = new Blob ([combinedData ], { type: ' application/octet-stream' });
101+
102+ // Create a download link and trigger the download
103+ const url = URL .createObjectURL (blob );
104+ const a = document .createElement (' a' );
105+ a .href = url ;
106+ a .download = fileName .value || ' downloaded_file' ;
107+ document .body .appendChild (a );
108+ a .click ();
109+
110+ // Clean up
111+ document .body .removeChild (a );
112+ URL .revokeObjectURL (url );
113+
114+ message .success (' 文件下载完成!' );
115+
116+ // Send download completion signal to server
117+ try {
118+ const pathParts = window .location .pathname .split (' /' );
119+ const fileId = pathParts [1 ]; // Assumes URL format is /{id}/file
120+
121+ const response = await fetch (` /api/fileflow/${fileId }/done ` , {
122+ method: ' PUT' ,
123+ headers: {
124+ ' Content-Type' : ' application/json' ,
125+ },
126+ body: JSON .stringify ({})
127+ });
128+
129+ if (response .ok ) {
130+ // Download completion signal sent successfully
131+ } else {
132+ message .warning (' 无法通知服务器下载完成,但文件已成功下载' );
133+ }
134+ } catch (error ) {
130135 message .warning (' 无法通知服务器下载完成,但文件已成功下载' );
131136 }
132137 } catch (error ) {
138+ message .error (' 保存文件时发生错误: ' + (error instanceof Error ? error .message : ' 未知错误' ));
139+ }
140+ } else {
141+ message .error (' 没有下载到任何文件数据' );
142+ }
143+
144+ isDownloading .value = false ;
145+ isFinished .value = true ;
146+ };
147+
148+ const rid = localStorage .getItem (" rid" ) || " " ;
149+ const p2pResult = await receiveViaWebRtc (fileId , rid , {
150+ onProgress : (percent ) => {
151+ downloadProgress .value = percent ;
152+ },
153+ onMetadata : (name , size ) => {
154+ if (! fileName .value ) fileName .value = name ;
155+ if (! fileSize .value ) fileSize .value = size ;
156+ },
157+ onStatus : (status ) => {
158+ if (status .startsWith (' fallback' )) {
159+ message .warning (' P2P 失败,切换到服务器下载' );
160+ }
161+ },
162+ });
163+
164+ if (p2pResult .status === ' success' ) {
165+ try {
166+ const response = await fetch (` /api/fileflow/${fileId }/done ` , {
167+ method: ' PUT' ,
168+ headers: {
169+ ' Content-Type' : ' application/json' ,
170+ },
171+ body: JSON .stringify ({})
172+ });
173+ if (! response .ok ) {
133174 message .warning (' 无法通知服务器下载完成,但文件已成功下载' );
134175 }
135- } catch ( error ) {
136- message .error ( ' 保存文件时发生错误: ' + ( error instanceof Error ? error . message : ' 未知错误 ' ) );
176+ } catch {
177+ message .warning ( ' 无法通知服务器下载完成,但文件已成功下载 ' );
137178 }
138- } else {
139- message .error (' 没有下载到任何文件数据' );
179+ isDownloading .value = false ;
180+ isFinished .value = true ;
181+ message .success (' 文件下载完成!' );
182+ return ;
140183 }
141-
142- isDownloading .value = false ;
143- isFinished .value = true ;
184+ await new Promise (resolve => setTimeout (resolve , 1000 ));
185+ await downloadViaHttp ();
144186}
145187
146188onMounted (async () => {
147189 if (localStorage .getItem (" rid" ) == null || localStorage .getItem (" rid" ) == " " || localStorage .getItem (" rid" ) == undefined ) {
148- localStorage .setItem (" rid" , Math .random ().toString (36 ).substring ( 16 ));
190+ localStorage .setItem (" rid" , Math .random ().toString (36 ).slice ( 2 , 10 ));
149191 }
150192
151193 // Get the file info from status API
@@ -154,7 +196,7 @@ onMounted(async () => {
154196 const pathParts = window .location .pathname .split (' /' );
155197 const fileId = pathParts [1 ]; // Assumes URL format is /{id}/file
156198
157- const response = await fetch (` /api/${fileId }/status ` );
199+ const response = await fetch (` /api/fileflow/ ${fileId }/status ` );
158200 const statusData = await response .json ();
159201
160202 if (! statusData .success ) {
@@ -310,4 +352,4 @@ onMounted(async () => {
310352:deep(.ant-progress-bg ) {
311353 border-radius : 10px ;
312354}
313- </style >
355+ </style >
0 commit comments