@@ -95,6 +95,10 @@ async function tryActivate(context: vscode.ExtensionContext) {
95
95
await activate ( context ) . catch ( log . error ) ;
96
96
} ) ;
97
97
98
+ ctx . registerCommand ( 'updateGithubToken' , ctx => async ( ) => {
99
+ await queryForGithubToken ( new PersistentState ( ctx . globalState ) ) ;
100
+ } ) ;
101
+
98
102
ctx . registerCommand ( 'analyzerStatus' , commands . analyzerStatus ) ;
99
103
ctx . registerCommand ( 'memoryUsage' , commands . memoryUsage ) ;
100
104
ctx . registerCommand ( 'reloadWorkspace' , commands . reloadWorkspace ) ;
@@ -173,7 +177,9 @@ async function bootstrapExtension(config: Config, state: PersistentState): Promi
173
177
if ( ! shouldCheckForNewNightly ) return ;
174
178
}
175
179
176
- const release = await fetchRelease ( "nightly" ) . catch ( ( e ) => {
180
+ const release = await downloadWithRetryDialog ( state , async ( ) => {
181
+ return await fetchRelease ( "nightly" , state . githubToken ) ;
182
+ } ) . catch ( ( e ) => {
177
183
log . error ( e ) ;
178
184
if ( state . releaseId === undefined ) { // Show error only for the initial download
179
185
vscode . window . showErrorMessage ( `Failed to download rust-analyzer nightly ${ e } ` ) ;
@@ -192,10 +198,14 @@ async function bootstrapExtension(config: Config, state: PersistentState): Promi
192
198
assert ( ! ! artifact , `Bad release: ${ JSON . stringify ( release ) } ` ) ;
193
199
194
200
const dest = path . join ( config . globalStoragePath , "rust-analyzer.vsix" ) ;
195
- await download ( {
196
- url : artifact . browser_download_url ,
197
- dest,
198
- progressTitle : "Downloading rust-analyzer extension" ,
201
+
202
+ await downloadWithRetryDialog ( state , async ( ) => {
203
+ await download ( {
204
+ url : artifact . browser_download_url ,
205
+ dest,
206
+ progressTitle : "Downloading rust-analyzer extension" ,
207
+ overwrite : true ,
208
+ } ) ;
199
209
} ) ;
200
210
201
211
await vscode . commands . executeCommand ( "workbench.extensions.installExtension" , vscode . Uri . file ( dest ) ) ;
@@ -308,21 +318,22 @@ async function getServer(config: Config, state: PersistentState): Promise<string
308
318
if ( userResponse !== "Download now" ) return dest ;
309
319
}
310
320
311
- const release = await fetchRelease ( config . package . releaseTag ) ;
321
+ const releaseTag = config . package . releaseTag ;
322
+ const release = await downloadWithRetryDialog ( state , async ( ) => {
323
+ return await fetchRelease ( releaseTag , state . githubToken ) ;
324
+ } ) ;
312
325
const artifact = release . assets . find ( artifact => artifact . name === `rust-analyzer-${ platform } .gz` ) ;
313
326
assert ( ! ! artifact , `Bad release: ${ JSON . stringify ( release ) } ` ) ;
314
327
315
- // Unlinking the exe file before moving new one on its place should prevent ETXTBSY error.
316
- await fs . unlink ( dest ) . catch ( err => {
317
- if ( err . code !== "ENOENT" ) throw err ;
318
- } ) ;
319
-
320
- await download ( {
321
- url : artifact . browser_download_url ,
322
- dest,
323
- progressTitle : "Downloading rust-analyzer server" ,
324
- gunzip : true ,
325
- mode : 0o755
328
+ await downloadWithRetryDialog ( state , async ( ) => {
329
+ await download ( {
330
+ url : artifact . browser_download_url ,
331
+ dest,
332
+ progressTitle : "Downloading rust-analyzer server" ,
333
+ gunzip : true ,
334
+ mode : 0o755 ,
335
+ overwrite : true ,
336
+ } ) ;
326
337
} ) ;
327
338
328
339
// Patching executable if that's NixOS.
@@ -333,3 +344,56 @@ async function getServer(config: Config, state: PersistentState): Promise<string
333
344
await state . updateServerVersion ( config . package . version ) ;
334
345
return dest ;
335
346
}
347
+
348
+ async function downloadWithRetryDialog < T > ( state : PersistentState , downloadFunc : ( ) => Promise < T > ) : Promise < T > {
349
+ while ( true ) {
350
+ try {
351
+ return await downloadFunc ( ) ;
352
+ } catch ( e ) {
353
+ const selected = await vscode . window . showErrorMessage ( "Failed to download: " + e . message , { } , {
354
+ title : "Update Github Auth Token" ,
355
+ updateToken : true ,
356
+ } , {
357
+ title : "Retry download" ,
358
+ retry : true ,
359
+ } , {
360
+ title : "Dismiss" ,
361
+ } ) ;
362
+
363
+ if ( selected ?. updateToken ) {
364
+ await queryForGithubToken ( state ) ;
365
+ continue ;
366
+ } else if ( selected ?. retry ) {
367
+ continue ;
368
+ }
369
+ throw e ;
370
+ } ;
371
+ }
372
+ }
373
+
374
+ async function queryForGithubToken ( state : PersistentState ) : Promise < void > {
375
+ const githubTokenOptions : vscode . InputBoxOptions = {
376
+ value : state . githubToken ,
377
+ password : true ,
378
+ prompt : `
379
+ This dialog allows to store a Github authorization token.
380
+ The usage of an authorization token will increase the rate
381
+ limit on the use of Github APIs and can thereby prevent getting
382
+ throttled.
383
+ Auth tokens can be created at https://github.com/settings/tokens` ,
384
+ } ;
385
+
386
+ const newToken = await vscode . window . showInputBox ( githubTokenOptions ) ;
387
+ if ( newToken === undefined ) {
388
+ // The user aborted the dialog => Do not update the stored token
389
+ return ;
390
+ }
391
+
392
+ if ( newToken === "" ) {
393
+ log . info ( "Clearing github token" ) ;
394
+ await state . updateGithubToken ( undefined ) ;
395
+ } else {
396
+ log . info ( "Storing new github token" ) ;
397
+ await state . updateGithubToken ( newToken ) ;
398
+ }
399
+ }
0 commit comments