@@ -137,69 +137,74 @@ export class PackageManager {
137
137
} ) ;
138
138
}
139
139
140
- private DownloadPackage ( pkg : IPackage ) : Promise < void > {
140
+ private async DownloadPackage ( pkg : IPackage ) : Promise < void > {
141
141
this . AppendChannel ( `Downloading package '${ pkg . description } ' ` ) ;
142
142
143
143
this . SetStatusText ( "$(cloud-download) Downloading packages..." ) ;
144
144
this . SetStatusTooltip ( `Downloading package '${ pkg . description } '...` ) ;
145
145
146
+ const tmpResult : tmp . SyncResult = await this . CreateTempFile ( pkg ) ;
147
+ await this . DownloadPackageWithRetries ( pkg , tmpResult ) ;
148
+ }
149
+
150
+ private async CreateTempFile ( pkg : IPackage ) : Promise < tmp . SyncResult > {
146
151
return new Promise < tmp . SyncResult > ( ( resolve , reject ) => {
147
152
tmp . file ( { prefix : "package-" } , ( err , path , fd , cleanupCallback ) => {
148
153
if ( err ) {
149
154
return reject ( new PackageManagerError ( 'Error from temp.file' , 'DownloadPackage' , pkg , err ) ) ;
150
155
}
151
156
152
- resolve ( < tmp . SyncResult > { name : path , fd : fd , removeCallback : cleanupCallback } ) ;
157
+ return resolve ( < tmp . SyncResult > { name : path , fd : fd , removeCallback : cleanupCallback } ) ;
153
158
} ) ;
154
- } )
155
- . then ( ( tmpResult ) => {
156
- pkg . tmpFile = tmpResult ;
157
-
158
- let lastError : any = null ;
159
- let retryCount : number = 0 ;
160
- let handleDownloadFailure : ( num : any , error : any ) => void = ( num , error ) => {
161
- retryCount = num ;
162
- lastError = error ;
159
+ } ) ;
160
+ }
161
+
162
+ private async DownloadPackageWithRetries ( pkg : IPackage , tmpResult : tmp . SyncResult ) : Promise < void > {
163
+ pkg . tmpFile = tmpResult ;
164
+
165
+ let success : boolean = false ;
166
+ let lastError : any = null ;
167
+ let retryCount : number = 0 ;
168
+ const MAX_RETRIES : number = 5 ;
169
+
170
+ // Retry the download at most MAX_RETRIES times with 2-32 seconds delay.
171
+ do {
172
+ try {
173
+ await this . DownloadFile ( pkg . url , pkg , retryCount ) ;
174
+ success = true ;
175
+ } catch ( error ) {
176
+ retryCount += 1 ;
177
+ lastError = error ;
178
+ if ( retryCount >= MAX_RETRIES ) {
179
+ this . AppendChannel ( ` Failed to download ` + pkg . url ) ;
180
+ throw error ;
181
+ } else {
182
+ // This will skip the success = true.
163
183
this . AppendChannel ( ` Failed. Retrying...` ) ;
164
- } ;
165
- // Retry the download at most 5 times with 2-32 seconds delay.
166
- return this . DownloadFile ( pkg . url , pkg , 0 ) . catch ( ( error ) => {
167
- handleDownloadFailure ( 1 , error ) ;
168
- return this . DownloadFile ( pkg . url , pkg , 1 ) . catch ( ( error ) => {
169
- handleDownloadFailure ( 2 , error ) ;
170
- return this . DownloadFile ( pkg . url , pkg , 2 ) . catch ( ( error ) => {
171
- handleDownloadFailure ( 3 , error ) ;
172
- return this . DownloadFile ( pkg . url , pkg , 3 ) . catch ( ( error ) => {
173
- handleDownloadFailure ( 4 , error ) ;
174
- return this . DownloadFile ( pkg . url , pkg , 4 ) . catch ( ( error ) => {
175
- handleDownloadFailure ( 5 , error ) ;
176
- return this . DownloadFile ( pkg . url , pkg , 5 ) ; // Last try, don't catch the error.
177
- } ) ;
178
- } ) ;
179
- } ) ;
180
- } ) ;
181
- } ) . then ( ( ) => {
182
- this . AppendLineChannel ( " Done!" ) ;
183
- if ( retryCount !== 0 ) {
184
- // Log telemetry to see if retrying helps.
185
- let telemetryProperties : { [ key : string ] : string } = { } ;
186
- telemetryProperties [ "success" ] = `OnRetry${ retryCount } ` ;
187
- if ( lastError instanceof PackageManagerError ) {
188
- let packageError : PackageManagerError = lastError ;
189
- telemetryProperties [ 'error.methodName' ] = packageError . methodName ;
190
- telemetryProperties [ 'error.message' ] = packageError . message ;
191
- if ( packageError . pkg ) {
192
- telemetryProperties [ 'error.packageName' ] = packageError . pkg . description ;
193
- telemetryProperties [ 'error.packageUrl' ] = packageError . pkg . url ;
194
- }
195
- if ( packageError . errorCode ) {
196
- telemetryProperties [ 'error.errorCode' ] = packageError . errorCode ;
197
- }
198
- }
199
- Telemetry . logDebuggerEvent ( "acquisition" , telemetryProperties ) ;
200
- }
201
- } ) ;
202
- } ) ;
184
+ continue ;
185
+ }
186
+ }
187
+ } while ( ! success && retryCount < MAX_RETRIES ) ;
188
+
189
+ this . AppendLineChannel ( " Done!" ) ;
190
+ if ( retryCount !== 0 ) {
191
+ // Log telemetry to see if retrying helps.
192
+ let telemetryProperties : { [ key : string ] : string } = { } ;
193
+ telemetryProperties [ "success" ] = `OnRetry${ retryCount } ` ;
194
+ if ( lastError instanceof PackageManagerError ) {
195
+ let packageError : PackageManagerError = lastError ;
196
+ telemetryProperties [ 'error.methodName' ] = packageError . methodName ;
197
+ telemetryProperties [ 'error.message' ] = packageError . message ;
198
+ if ( packageError . pkg ) {
199
+ telemetryProperties [ 'error.packageName' ] = packageError . pkg . description ;
200
+ telemetryProperties [ 'error.packageUrl' ] = packageError . pkg . url ;
201
+ }
202
+ if ( packageError . errorCode ) {
203
+ telemetryProperties [ 'error.errorCode' ] = packageError . errorCode ;
204
+ }
205
+ }
206
+ Telemetry . logDebuggerEvent ( "acquisition" , telemetryProperties ) ;
207
+ }
203
208
}
204
209
205
210
// reloadCpptoolsJson in main.ts uses ~25% of this function.
@@ -276,11 +281,11 @@ export class PackageManager {
276
281
} ) ;
277
282
278
283
response . on ( 'end' , ( ) => {
279
- resolve ( ) ;
284
+ return resolve ( ) ;
280
285
} ) ;
281
286
282
287
response . on ( 'error' , ( error ) => {
283
- reject ( new PackageManagerWebResponseError ( response . socket , 'HTTP/HTTPS Response error' , 'DownloadFile' , pkg , error . stack , error . name ) ) ;
288
+ return reject ( new PackageManagerWebResponseError ( response . socket , 'HTTP/HTTPS Response error' , 'DownloadFile' , pkg , error . stack , error . name ) ) ;
284
289
} ) ;
285
290
286
291
// Begin piping data from the response to the package file
@@ -291,7 +296,7 @@ export class PackageManager {
291
296
let request : ClientRequest = https . request ( options , handleHttpResponse ) ;
292
297
293
298
request . on ( 'error' , ( error ) => {
294
- reject ( new PackageManagerError ( 'HTTP/HTTPS Request error' + ( urlString . includes ( "fwlink" ) ? ": fwlink" : "" ) , 'DownloadFile' , pkg , error . stack , error . message ) ) ;
299
+ return reject ( new PackageManagerError ( 'HTTP/HTTPS Request error' + ( urlString . includes ( "fwlink" ) ? ": fwlink" : "" ) , 'DownloadFile' , pkg , error . stack , error . message ) ) ;
295
300
} ) ;
296
301
297
302
// Execute the request
@@ -316,6 +321,15 @@ export class PackageManager {
316
321
return reject ( new PackageManagerError ( 'Zip file error' , 'InstallPackage' , pkg , err ) ) ;
317
322
}
318
323
324
+ // setup zip file events
325
+ zipfile . on ( 'end' , ( ) => {
326
+ return resolve ( ) ;
327
+ } ) ;
328
+
329
+ zipfile . on ( 'error' , err => {
330
+ return reject ( new PackageManagerError ( 'Zip File Error' , 'InstallPackage' , pkg , err , err . code ) ) ;
331
+ } ) ;
332
+
319
333
zipfile . readEntry ( ) ;
320
334
321
335
zipfile . on ( 'entry' , ( entry : yauzl . Entry ) => {
@@ -334,26 +348,52 @@ export class PackageManager {
334
348
util . checkFileExists ( absoluteEntryPath ) . then ( ( exists : boolean ) => {
335
349
if ( ! exists ) {
336
350
// File - extract it
337
- zipfile . openReadStream ( entry , ( err , readStream ) => {
351
+ zipfile . openReadStream ( entry , ( err , readStream : fs . ReadStream ) => {
338
352
if ( err ) {
339
353
return reject ( new PackageManagerError ( 'Error reading zip stream' , 'InstallPackage' , pkg , err ) ) ;
340
354
}
341
355
342
- mkdirp . mkdirp ( path . dirname ( absoluteEntryPath ) , { mode : 0o775 } , ( err ) => {
356
+ readStream . on ( 'error' , ( err ) => {
357
+ return reject ( new PackageManagerError ( 'Error in readStream' , 'InstallPackage' , pkg , err ) ) ;
358
+ } ) ;
359
+
360
+ mkdirp . mkdirp ( path . dirname ( absoluteEntryPath ) , { mode : 0o775 } , async ( err ) => {
343
361
if ( err ) {
344
362
return reject ( new PackageManagerError ( 'Error creating directory' , 'InstallPackage' , pkg , err , err . code ) ) ;
345
363
}
346
364
365
+ // Create as a .tmp file to avoid partially unzipped files
366
+ // counting as completed files.
367
+ let absoluteEntryTempFile : string = absoluteEntryPath + ".tmp" ;
368
+ if ( fs . existsSync ( absoluteEntryTempFile ) ) {
369
+ try {
370
+ await util . unlinkPromise ( absoluteEntryTempFile ) ;
371
+ } catch ( err ) {
372
+ return reject ( new PackageManagerError ( `Error unlinking file ${ absoluteEntryTempFile } ` , 'InstallPackage' , pkg , err ) ) ;
373
+ }
374
+ }
375
+
347
376
// Make sure executable files have correct permissions when extracted
348
377
let fileMode : number = ( pkg . binaries && pkg . binaries . indexOf ( absoluteEntryPath ) !== - 1 ) ? 0o755 : 0o664 ;
349
-
350
- let writeStream : fs . WriteStream = fs . createWriteStream ( absoluteEntryPath , { mode : fileMode } ) ;
351
- readStream . pipe ( writeStream ) ;
352
- writeStream . on ( 'close' , ( ) => {
378
+ let writeStream : fs . WriteStream = fs . createWriteStream ( absoluteEntryTempFile , { mode : fileMode } ) ;
379
+
380
+ writeStream . on ( 'close' , async ( ) => {
381
+ try {
382
+ // Remove .tmp extension from the file.
383
+ await util . renamePromise ( absoluteEntryTempFile , absoluteEntryPath ) ;
384
+ } catch ( err ) {
385
+ return reject ( new PackageManagerError ( `Error renaming file ${ absoluteEntryTempFile } ` , 'InstallPackage' , pkg , err ) ) ;
386
+ }
353
387
// Wait till output is done writing before reading the next zip entry.
354
388
// Otherwise, it's possible to try to launch the .exe before it is done being created.
355
389
zipfile . readEntry ( ) ;
356
390
} ) ;
391
+
392
+ writeStream . on ( 'error' , ( err ) => {
393
+ return reject ( new PackageManagerError ( 'Error in writeStream' , 'InstallPackage' , pkg , err ) ) ;
394
+ } ) ;
395
+
396
+ readStream . pipe ( writeStream ) ;
357
397
} ) ;
358
398
} ) ;
359
399
} else {
@@ -366,20 +406,11 @@ export class PackageManager {
366
406
} ) ;
367
407
}
368
408
} ) ;
369
-
370
- zipfile . on ( 'end' , ( ) => {
371
- resolve ( ) ;
372
- } ) ;
373
-
374
- zipfile . on ( 'error' , err => {
375
- reject ( new PackageManagerError ( 'Zip File Error' , 'InstallPackage' , pkg , err , err . code ) ) ;
376
- } ) ;
377
- } ) ;
378
- } )
379
- . then ( ( ) => {
380
- // Clean up temp file
381
- pkg . tmpFile . removeCallback ( ) ;
382
409
} ) ;
410
+ } ) . then ( ( ) => {
411
+ // Clean up temp file
412
+ pkg . tmpFile . removeCallback ( ) ;
413
+ } ) ;
383
414
}
384
415
385
416
private AppendChannel ( text : string ) : void {
0 commit comments