@@ -8,6 +8,7 @@ import { Memento } from 'vscode';
8
8
import { IExtensionSingleActivationService } from '../activation/types' ;
9
9
import { ICommandManager } from './application/types' ;
10
10
import { Commands } from './constants' ;
11
+ import { traceError , traceVerbose } from './logger' ;
11
12
import {
12
13
GLOBAL_MEMENTO ,
13
14
IExtensionContext ,
@@ -16,6 +17,7 @@ import {
16
17
IPersistentStateFactory ,
17
18
WORKSPACE_MEMENTO ,
18
19
} from './types' ;
20
+ import { cache } from './utils/decorators' ;
19
21
20
22
export class PersistentState < T > implements IPersistentState < T > {
21
23
constructor (
@@ -39,30 +41,39 @@ export class PersistentState<T> implements IPersistentState<T> {
39
41
}
40
42
41
43
public async updateValue ( newValue : T ) : Promise < void > {
42
- if ( this . expiryDurationMs ) {
43
- await this . storage . update ( this . key , { data : newValue , expiry : Date . now ( ) + this . expiryDurationMs } ) ;
44
- } else {
45
- await this . storage . update ( this . key , newValue ) ;
44
+ try {
45
+ if ( this . expiryDurationMs ) {
46
+ await this . storage . update ( this . key , { data : newValue , expiry : Date . now ( ) + this . expiryDurationMs } ) ;
47
+ } else {
48
+ await this . storage . update ( this . key , newValue ) ;
49
+ }
50
+ } catch ( ex ) {
51
+ traceError ( 'Error while updating storage for key:' , this . key , ex ) ;
46
52
}
47
53
}
48
54
}
49
55
50
- const GLOBAL_PERSISTENT_KEYS = 'PYTHON_EXTENSION_GLOBAL_STORAGE_KEYS' ;
51
- const WORKSPACE_PERSISTENT_KEYS = 'PYTHON_EXTENSION_WORKSPACE_STORAGE_KEYS' ;
52
- type keysStorage = { key : string ; defaultValue : unknown } ;
56
+ const GLOBAL_PERSISTENT_KEYS_DEPRECATED = 'PYTHON_EXTENSION_GLOBAL_STORAGE_KEYS' ;
57
+ const WORKSPACE_PERSISTENT_KEYS_DEPRECATED = 'PYTHON_EXTENSION_WORKSPACE_STORAGE_KEYS' ;
58
+
59
+ const GLOBAL_PERSISTENT_KEYS = 'PYTHON_GLOBAL_STORAGE_KEYS' ;
60
+ const WORKSPACE_PERSISTENT_KEYS = 'PYTHON_WORKSPACE_STORAGE_KEYS' ;
61
+ type KeysStorageType = 'global' | 'workspace' ;
62
+ type KeysStorage = { key : string ; defaultValue : unknown } ;
53
63
54
64
@injectable ( )
55
65
export class PersistentStateFactory implements IPersistentStateFactory , IExtensionSingleActivationService {
56
- private readonly globalKeysStorage = new PersistentState < keysStorage [ ] > (
66
+ public readonly _globalKeysStorage = new PersistentState < KeysStorage [ ] > (
57
67
this . globalState ,
58
68
GLOBAL_PERSISTENT_KEYS ,
59
69
[ ] ,
60
70
) ;
61
- private readonly workspaceKeysStorage = new PersistentState < keysStorage [ ] > (
71
+ public readonly _workspaceKeysStorage = new PersistentState < KeysStorage [ ] > (
62
72
this . workspaceState ,
63
73
WORKSPACE_PERSISTENT_KEYS ,
64
74
[ ] ,
65
75
) ;
76
+ private cleanedOnce = false ;
66
77
constructor (
67
78
@inject ( IMemento ) @named ( GLOBAL_MEMENTO ) private globalState : Memento ,
68
79
@inject ( IMemento ) @named ( WORKSPACE_MEMENTO ) private workspaceState : Memento ,
@@ -71,16 +82,27 @@ export class PersistentStateFactory implements IPersistentStateFactory, IExtensi
71
82
72
83
public async activate ( ) : Promise < void > {
73
84
this . cmdManager . registerCommand ( Commands . ClearStorage , this . cleanAllPersistentStates . bind ( this ) ) ;
85
+ const globalKeysStorageDeprecated = this . createGlobalPersistentState ( GLOBAL_PERSISTENT_KEYS_DEPRECATED , [ ] ) ;
86
+ const workspaceKeysStorageDeprecated = this . createWorkspacePersistentState (
87
+ WORKSPACE_PERSISTENT_KEYS_DEPRECATED ,
88
+ [ ] ,
89
+ ) ;
90
+ // Old storages have grown to be unusually large due to https://github.com/microsoft/vscode-python/issues/17488,
91
+ // so reset them. This line can be removed after a while.
92
+ if ( globalKeysStorageDeprecated . value . length > 0 ) {
93
+ globalKeysStorageDeprecated . updateValue ( [ ] ) . ignoreErrors ( ) ;
94
+ }
95
+ if ( workspaceKeysStorageDeprecated . value . length > 0 ) {
96
+ workspaceKeysStorageDeprecated . updateValue ( [ ] ) . ignoreErrors ( ) ;
97
+ }
74
98
}
75
99
76
100
public createGlobalPersistentState < T > (
77
101
key : string ,
78
102
defaultValue ?: T ,
79
103
expiryDurationMs ?: number ,
80
104
) : IPersistentState < T > {
81
- if ( ! this . globalKeysStorage . value . includes ( { key, defaultValue } ) ) {
82
- this . globalKeysStorage . updateValue ( [ { key, defaultValue } , ...this . globalKeysStorage . value ] ) . ignoreErrors ( ) ;
83
- }
105
+ this . addKeyToStorage ( 'global' , key , defaultValue ) . ignoreErrors ( ) ;
84
106
return new PersistentState < T > ( this . globalState , key , defaultValue , expiryDurationMs ) ;
85
107
}
86
108
@@ -89,29 +111,44 @@ export class PersistentStateFactory implements IPersistentStateFactory, IExtensi
89
111
defaultValue ?: T ,
90
112
expiryDurationMs ?: number ,
91
113
) : IPersistentState < T > {
92
- if ( ! this . workspaceKeysStorage . value . includes ( { key, defaultValue } ) ) {
93
- this . workspaceKeysStorage
94
- . updateValue ( [ { key, defaultValue } , ...this . workspaceKeysStorage . value ] )
95
- . ignoreErrors ( ) ;
96
- }
114
+ this . addKeyToStorage ( 'workspace' , key , defaultValue ) . ignoreErrors ( ) ;
97
115
return new PersistentState < T > ( this . workspaceState , key , defaultValue , expiryDurationMs ) ;
98
116
}
99
117
118
+ /**
119
+ * Note we use a decorator to cache the promise returned by this method, so it's only called once.
120
+ * It is only cached for the particular arguments passed, so the argument type is simplified here.
121
+ */
122
+ @cache ( - 1 , true )
123
+ private async addKeyToStorage < T > ( keyStorageType : KeysStorageType , key : string , defaultValue ?: T ) {
124
+ const storage = keyStorageType === 'global' ? this . _globalKeysStorage : this . _workspaceKeysStorage ;
125
+ const found = storage . value . find ( ( value ) => value . key === key && value . defaultValue === defaultValue ) ;
126
+ if ( ! found ) {
127
+ await storage . updateValue ( [ { key, defaultValue } , ...storage . value ] ) ;
128
+ }
129
+ }
130
+
100
131
private async cleanAllPersistentStates ( ) : Promise < void > {
132
+ if ( this . cleanedOnce ) {
133
+ traceError ( 'Storage can only be cleaned once per session, reload window.' ) ;
134
+ return ;
135
+ }
101
136
await Promise . all (
102
- this . globalKeysStorage . value . map ( async ( keyContent ) => {
137
+ this . _globalKeysStorage . value . map ( async ( keyContent ) => {
103
138
const storage = this . createGlobalPersistentState ( keyContent . key ) ;
104
139
await storage . updateValue ( keyContent . defaultValue ) ;
105
140
} ) ,
106
141
) ;
107
142
await Promise . all (
108
- this . workspaceKeysStorage . value . map ( async ( keyContent ) => {
143
+ this . _workspaceKeysStorage . value . map ( async ( keyContent ) => {
109
144
const storage = this . createWorkspacePersistentState ( keyContent . key ) ;
110
145
await storage . updateValue ( keyContent . defaultValue ) ;
111
146
} ) ,
112
147
) ;
113
- await this . globalKeysStorage . updateValue ( [ ] ) ;
114
- await this . workspaceKeysStorage . updateValue ( [ ] ) ;
148
+ await this . _globalKeysStorage . updateValue ( [ ] ) ;
149
+ await this . _workspaceKeysStorage . updateValue ( [ ] ) ;
150
+ this . cleanedOnce = true ;
151
+ traceVerbose ( 'Finished clearing storage.' ) ;
115
152
}
116
153
}
117
154
@@ -128,9 +165,11 @@ interface IPersistentStorage<T> {
128
165
* Build a global storage object for the given key.
129
166
*/
130
167
export function getGlobalStorage < T > ( context : IExtensionContext , key : string , defaultValue ?: T ) : IPersistentStorage < T > {
131
- const globalKeysStorage = new PersistentState < keysStorage [ ] > ( context . globalState , GLOBAL_PERSISTENT_KEYS , [ ] ) ;
132
- if ( ! globalKeysStorage . value . includes ( { key, defaultValue : undefined } ) ) {
133
- globalKeysStorage . updateValue ( [ { key, defaultValue : undefined } , ...globalKeysStorage . value ] ) . ignoreErrors ( ) ;
168
+ const globalKeysStorage = new PersistentState < KeysStorage [ ] > ( context . globalState , GLOBAL_PERSISTENT_KEYS , [ ] ) ;
169
+ const found = globalKeysStorage . value . find ( ( value ) => value . key === key && value . defaultValue === defaultValue ) ;
170
+ if ( ! found ) {
171
+ const newValue = [ { key, defaultValue } , ...globalKeysStorage . value ] ;
172
+ globalKeysStorage . updateValue ( newValue ) . ignoreErrors ( ) ;
134
173
}
135
174
const raw = new PersistentState < T > ( context . globalState , key , defaultValue ) ;
136
175
return {
0 commit comments