@@ -18,7 +18,6 @@ interface ZipFile {
1818export class DownloadTask {
1919 private context : common . Context ;
2020 private hash : string ;
21- private readonly DOWNLOAD_CHUNK_SIZE = 4096 ;
2221 private eventHub : EventHub ;
2322
2423 constructor ( context : common . Context ) {
@@ -53,6 +52,35 @@ export class DownloadTask {
5352 private async downloadFile ( params : DownloadTaskParams ) : Promise < void > {
5453 const httpRequest = http . createHttp ( ) ;
5554 this . hash = params . hash ;
55+ let writer : fileIo . File | null = null ;
56+ let contentLength = 0 ;
57+ let received = 0 ;
58+ let writeError : Error | null = null ;
59+ let writeQueue = Promise . resolve ( ) ;
60+
61+ const closeWriter = async ( ) => {
62+ if ( writer ) {
63+ await fileIo . close ( writer ) ;
64+ writer = null ;
65+ }
66+ } ;
67+
68+ const dataEndPromise = new Promise < void > ( ( resolve , reject ) => {
69+ httpRequest . on ( 'dataEnd' , ( ) => {
70+ writeQueue
71+ . then ( async ( ) => {
72+ if ( writeError ) {
73+ throw writeError ;
74+ }
75+ await closeWriter ( ) ;
76+ resolve ( ) ;
77+ } )
78+ . catch ( async error => {
79+ await closeWriter ( ) ;
80+ reject ( error ) ;
81+ } ) ;
82+ } ) ;
83+ } ) ;
5684
5785 try {
5886 let exists = fileIo . accessSync ( params . targetFile ) ;
@@ -69,40 +97,74 @@ export class DownloadTask {
6997 }
7098 }
7199
72- const response = await httpRequest . request ( params . url , {
100+ writer = await fileIo . open (
101+ params . targetFile ,
102+ fileIo . OpenMode . CREATE | fileIo . OpenMode . READ_WRITE ,
103+ ) ;
104+
105+ httpRequest . on ( 'headersReceive' , ( header : Record < string , string > ) => {
106+ if ( ! header ) {
107+ return ;
108+ }
109+ const lengthKey = Object . keys ( header ) . find (
110+ key => key . toLowerCase ( ) === 'content-length' ,
111+ ) ;
112+ if ( ! lengthKey ) {
113+ return ;
114+ }
115+ const length = parseInt ( header [ lengthKey ] , 10 ) ;
116+ if ( ! Number . isNaN ( length ) ) {
117+ contentLength = length ;
118+ }
119+ } ) ;
120+
121+ httpRequest . on ( 'dataReceive' , ( data : ArrayBuffer ) => {
122+ if ( writeError ) {
123+ return ;
124+ }
125+ received += data . byteLength ;
126+ writeQueue = writeQueue . then ( async ( ) => {
127+ if ( ! writer || writeError ) {
128+ return ;
129+ }
130+ try {
131+ await fileIo . write ( writer . fd , data ) ;
132+ } catch ( error ) {
133+ writeError = error as Error ;
134+ }
135+ } ) ;
136+ this . onProgressUpdate ( received , contentLength ) ;
137+ } ) ;
138+
139+ httpRequest . on (
140+ 'dataReceiveProgress' ,
141+ ( data : http . DataReceiveProgressInfo ) => {
142+ if ( data . totalSize > 0 ) {
143+ contentLength = data . totalSize ;
144+ }
145+ if ( data . receiveSize > received ) {
146+ received = data . receiveSize ;
147+ }
148+ this . onProgressUpdate ( received , contentLength ) ;
149+ } ,
150+ ) ;
151+
152+ const responseCode = await httpRequest . requestInStream ( params . url , {
73153 method : http . RequestMethod . GET ,
74154 readTimeout : 60000 ,
75155 connectTimeout : 60000 ,
76156 header : {
77157 'Content-Type' : 'application/octet-stream' ,
78158 } ,
79159 } ) ;
80- if ( response . responseCode > 299 ) {
81- throw Error ( `Server error: ${ response . responseCode } ` ) ;
160+ if ( responseCode > 299 ) {
161+ throw Error ( `Server error: ${ responseCode } ` ) ;
82162 }
83163
84- const contentLength = parseInt ( response . header [ 'content-length' ] || '0' ) ;
85- const writer = await fileIo . open (
86- params . targetFile ,
87- fileIo . OpenMode . CREATE | fileIo . OpenMode . READ_WRITE ,
88- ) ;
89- let received = 0 ;
90- const data = response . result as ArrayBuffer ;
91- const chunks = Math . ceil ( data . byteLength / this . DOWNLOAD_CHUNK_SIZE ) ;
92- for ( let i = 0 ; i < chunks ; i ++ ) {
93- const start = i * this . DOWNLOAD_CHUNK_SIZE ;
94- const end = Math . min ( start + this . DOWNLOAD_CHUNK_SIZE , data . byteLength ) ;
95- const chunk = data . slice ( start , end ) ;
96-
97- await fileIo . write ( writer . fd , chunk ) ;
98- received += chunk . byteLength ;
99-
100- this . onProgressUpdate ( received , contentLength ) ;
101- }
102- await fileIo . close ( writer ) ;
164+ await dataEndPromise ;
103165 const stats = await fileIo . stat ( params . targetFile ) ;
104166 const fileSize = stats . size ;
105- if ( fileSize !== contentLength ) {
167+ if ( contentLength > 0 && fileSize !== contentLength ) {
106168 throw Error (
107169 `Download incomplete: expected ${ contentLength } bytes but got ${ stats . size } bytes` ,
108170 ) ;
@@ -111,6 +173,15 @@ export class DownloadTask {
111173 console . error ( 'Download failed:' , error ) ;
112174 throw error ;
113175 } finally {
176+ try {
177+ await closeWriter ( ) ;
178+ } catch ( closeError ) {
179+ console . error ( 'Failed to close file:' , closeError ) ;
180+ }
181+ httpRequest . off ( 'headersReceive' ) ;
182+ httpRequest . off ( 'dataReceive' ) ;
183+ httpRequest . off ( 'dataReceiveProgress' ) ;
184+ httpRequest . off ( 'dataEnd' ) ;
114185 httpRequest . destroy ( ) ;
115186 }
116187 }
0 commit comments