3
3
createWriteStream ,
4
4
mkdtemp ,
5
5
pathExists ,
6
+ readJson ,
6
7
remove ,
7
8
writeJson ,
8
9
} from "fs-extra" ;
@@ -25,7 +26,9 @@ import {
25
26
InvocationRateLimiter ,
26
27
InvocationRateLimiterResultKind ,
27
28
} from "../common/invocation-rate-limiter" ;
29
+ import type { NotificationLogger } from "../common/logging" ;
28
30
import {
31
+ showAndLogExceptionWithTelemetry ,
29
32
showAndLogErrorMessage ,
30
33
showAndLogWarningMessage ,
31
34
} from "../common/logging" ;
@@ -35,6 +38,10 @@ import type { Release } from "./distribution/release";
35
38
import { ReleasesApiConsumer } from "./distribution/releases-api-consumer" ;
36
39
import { createTimeoutSignal } from "../common/fetch-stream" ;
37
40
import { withDistributionUpdateLock } from "./lock" ;
41
+ import { asError , getErrorMessage } from "../common/helpers-pure" ;
42
+ import { isIOError } from "../common/files" ;
43
+ import { telemetryListener } from "../common/vscode/telemetry" ;
44
+ import { redactableError } from "../common/errors" ;
38
45
39
46
/**
40
47
* distribution.ts
@@ -60,6 +67,11 @@ const NIGHTLY_DISTRIBUTION_REPOSITORY_NWO = "dsp-testing/codeql-cli-nightlies";
60
67
*/
61
68
export const DEFAULT_DISTRIBUTION_VERSION_RANGE : Range = new Range ( "2.x" ) ;
62
69
70
+ interface DistributionState {
71
+ folderIndex : number ;
72
+ release : Release | null ;
73
+ }
74
+
63
75
export interface DistributionProvider {
64
76
getCodeQlPathWithoutVersionCheck ( ) : Promise < string | undefined > ;
65
77
onDidChangeDistribution ?: Event < void > ;
@@ -71,13 +83,15 @@ export class DistributionManager implements DistributionProvider {
71
83
public readonly config : DistributionConfig ,
72
84
private readonly versionRange : Range ,
73
85
extensionContext : ExtensionContext ,
86
+ logger : NotificationLogger ,
74
87
) {
75
88
this . _onDidChangeDistribution = config . onDidChangeConfiguration ;
76
89
this . extensionSpecificDistributionManager =
77
90
new ExtensionSpecificDistributionManager (
78
91
config ,
79
92
versionRange ,
80
93
extensionContext ,
94
+ logger ,
81
95
) ;
82
96
this . updateCheckRateLimiter = new InvocationRateLimiter (
83
97
extensionContext . globalState ,
@@ -87,6 +101,10 @@ export class DistributionManager implements DistributionProvider {
87
101
) ;
88
102
}
89
103
104
+ public async initialize ( ) : Promise < void > {
105
+ await this . extensionSpecificDistributionManager . initialize ( ) ;
106
+ }
107
+
90
108
/**
91
109
* Look up a CodeQL launcher binary.
92
110
*/
@@ -287,14 +305,53 @@ export class DistributionManager implements DistributionProvider {
287
305
}
288
306
289
307
class ExtensionSpecificDistributionManager {
308
+ private distributionState : DistributionState | undefined ;
309
+
290
310
constructor (
291
311
private readonly config : DistributionConfig ,
292
312
private readonly versionRange : Range ,
293
313
private readonly extensionContext : ExtensionContext ,
314
+ private readonly logger : NotificationLogger ,
294
315
) {
295
316
/**/
296
317
}
297
318
319
+ public async initialize ( ) {
320
+ const distributionStatePath = this . getDistributionStatePath ( ) ;
321
+ try {
322
+ this . distributionState = await readJson ( distributionStatePath ) ;
323
+ } catch ( e : unknown ) {
324
+ if ( isIOError ( e ) && e . code === "ENOENT" ) {
325
+ // If the file doesn't exist, that just means we need to create it
326
+
327
+ this . distributionState = {
328
+ folderIndex : this . extensionContext . globalState . get (
329
+ ExtensionSpecificDistributionManager . _currentDistributionFolderIndexStateKey ,
330
+ 0 ,
331
+ ) ,
332
+ release : ( this . extensionContext . globalState . get (
333
+ ExtensionSpecificDistributionManager . _installedReleaseStateKey ,
334
+ ) ?? null ) as Release | null ,
335
+ } ;
336
+
337
+ // This may result in a race condition, but when this happens both processes should write the same file.
338
+ await writeJson ( distributionStatePath , this . distributionState ) ;
339
+ } else {
340
+ void showAndLogExceptionWithTelemetry (
341
+ this . logger ,
342
+ telemetryListener ,
343
+ redactableError (
344
+ asError ( e ) ,
345
+ ) `Failed to read distribution state from ${ distributionStatePath } : ${ getErrorMessage ( e ) } ` ,
346
+ ) ;
347
+ this . distributionState = {
348
+ folderIndex : 0 ,
349
+ release : null ,
350
+ } ;
351
+ }
352
+ }
353
+ }
354
+
298
355
public async getCodeQlPathWithoutVersionCheck ( ) : Promise < string | undefined > {
299
356
if ( this . getInstalledRelease ( ) !== undefined ) {
300
357
// An extension specific distribution has been installed.
@@ -357,14 +414,7 @@ class ExtensionSpecificDistributionManager {
357
414
release : Release ,
358
415
progressCallback ?: ProgressCallback ,
359
416
) : Promise < void > {
360
- const distributionStatePath = join (
361
- this . extensionContext . globalStorageUri . fsPath ,
362
- ExtensionSpecificDistributionManager . _distributionStateFilename ,
363
- ) ;
364
- if ( ! ( await pathExists ( distributionStatePath ) ) ) {
365
- // This may result in a race condition, but when this happens both processes should write the same file.
366
- await writeJson ( distributionStatePath , { } ) ;
367
- }
417
+ const distributionStatePath = this . getDistributionStatePath ( ) ;
368
418
369
419
await withDistributionUpdateLock (
370
420
// .lock will be appended to this filename
@@ -586,23 +636,19 @@ class ExtensionSpecificDistributionManager {
586
636
}
587
637
588
638
private async bumpDistributionFolderIndex ( ) : Promise < void > {
589
- const index = this . extensionContext . globalState . get (
590
- ExtensionSpecificDistributionManager . _currentDistributionFolderIndexStateKey ,
591
- 0 ,
592
- ) ;
593
- await this . extensionContext . globalState . update (
594
- ExtensionSpecificDistributionManager . _currentDistributionFolderIndexStateKey ,
595
- index + 1 ,
596
- ) ;
639
+ await this . updateState ( ( oldState ) => {
640
+ return {
641
+ ...oldState ,
642
+ folderIndex : oldState . folderIndex + 1 ,
643
+ } ;
644
+ } ) ;
597
645
}
598
646
599
647
private getDistributionStoragePath ( ) : string {
648
+ const distributionState = this . getDistributionState ( ) ;
649
+
600
650
// Use an empty string for the initial distribution for backwards compatibility.
601
- const distributionFolderIndex =
602
- this . extensionContext . globalState . get (
603
- ExtensionSpecificDistributionManager . _currentDistributionFolderIndexStateKey ,
604
- 0 ,
605
- ) || "" ;
651
+ const distributionFolderIndex = distributionState . folderIndex || "" ;
606
652
return join (
607
653
this . extensionContext . globalStorageUri . fsPath ,
608
654
ExtensionSpecificDistributionManager . _currentDistributionFolderBaseName +
@@ -617,19 +663,50 @@ class ExtensionSpecificDistributionManager {
617
663
) ;
618
664
}
619
665
620
- private getInstalledRelease ( ) : Release | undefined {
621
- return this . extensionContext . globalState . get (
622
- ExtensionSpecificDistributionManager . _installedReleaseStateKey ,
666
+ private getDistributionStatePath ( ) : string {
667
+ return join (
668
+ this . extensionContext . globalStorageUri . fsPath ,
669
+ ExtensionSpecificDistributionManager . _distributionStateFilename ,
623
670
) ;
624
671
}
625
672
673
+ private getInstalledRelease ( ) : Release | undefined {
674
+ return this . getDistributionState ( ) . release ?? undefined ;
675
+ }
676
+
626
677
private async storeInstalledRelease (
627
678
release : Release | undefined ,
628
679
) : Promise < void > {
629
- await this . extensionContext . globalState . update (
630
- ExtensionSpecificDistributionManager . _installedReleaseStateKey ,
631
- release ,
632
- ) ;
680
+ await this . updateState ( ( oldState ) => ( {
681
+ ...oldState ,
682
+ release : release ?? null ,
683
+ } ) ) ;
684
+ }
685
+
686
+ private getDistributionState ( ) : DistributionState {
687
+ const distributionState = this . distributionState ;
688
+ if ( distributionState === undefined ) {
689
+ throw new Error (
690
+ "Invariant violation: distribution state not initialized" ,
691
+ ) ;
692
+ }
693
+ return distributionState ;
694
+ }
695
+
696
+ private async updateState (
697
+ f : ( oldState : DistributionState ) => DistributionState ,
698
+ ) {
699
+ const oldState = this . distributionState ;
700
+ if ( oldState === undefined ) {
701
+ throw new Error (
702
+ "Invariant violation: distribution state not initialized" ,
703
+ ) ;
704
+ }
705
+ const newState = f ( oldState ) ;
706
+ this . distributionState = newState ;
707
+
708
+ const distributionStatePath = this . getDistributionStatePath ( ) ;
709
+ await writeJson ( distributionStatePath , newState ) ;
633
710
}
634
711
635
712
private static readonly _currentDistributionFolderBaseName = "distribution" ;
0 commit comments