3
3
4
4
import { Event , EventEmitter } from 'vscode' ;
5
5
import '../../../../common/extensions' ;
6
- import { createDeferred } from '../../../../common/utils/async' ;
6
+ import { createDeferred , Deferred } from '../../../../common/utils/async' ;
7
7
import { traceError } from '../../../../logging' ;
8
8
import { normalizePath } from '../../../common/externalDependencies' ;
9
9
import { PythonEnvInfo } from '../../info' ;
10
10
import {
11
+ GetRefreshEnvironmentsOptions ,
11
12
IDiscoveryAPI ,
12
13
IResolvingLocator ,
13
14
isProgressEvent ,
@@ -24,21 +25,22 @@ import { IEnvsCollectionCache } from './envsCollectionCache';
24
25
*/
25
26
export class EnvsCollectionService extends PythonEnvsWatcher < PythonEnvCollectionChangedEvent > implements IDiscoveryAPI {
26
27
/** Keeps track of ongoing refreshes for various queries. */
27
- private refreshPromises = new Map < PythonLocatorQuery | undefined , Promise < void > > ( ) ;
28
+ private refreshDeferreds = new Map < PythonLocatorQuery | undefined , Deferred < void > > ( ) ;
28
29
29
30
/** Keeps track of scheduled refreshes other than the ongoing one for various queries. */
30
31
private scheduledRefreshes = new Map < PythonLocatorQuery | undefined , Promise < void > > ( ) ;
31
32
33
+ private refreshStageDeferreds = new Map < ProgressReportStage , Deferred < void > > ( ) ;
34
+
32
35
private readonly progress = new EventEmitter < ProgressNotificationEvent > ( ) ;
33
36
34
37
public get onProgress ( ) : Event < ProgressNotificationEvent > {
35
38
return this . progress . event ;
36
39
}
37
40
38
- public getRefreshPromise ( ) : Promise < void > | undefined {
39
- return this . refreshPromises . size > 0
40
- ? Promise . all ( Array . from ( this . refreshPromises . values ( ) ) ) . then ( )
41
- : undefined ;
41
+ public getRefreshPromise ( options ?: GetRefreshEnvironmentsOptions ) : Promise < void > | undefined {
42
+ const stage = options ?. stage ?? ProgressReportStage . discoveryFinished ;
43
+ return this . refreshStageDeferreds . get ( stage ) ?. promise ;
42
44
}
43
45
44
46
constructor ( private readonly cache : IEnvsCollectionCache , private readonly locator : IResolvingLocator ) {
@@ -58,6 +60,10 @@ export class EnvsCollectionService extends PythonEnvsWatcher<PythonEnvCollection
58
60
this . cache . onChanged ( ( e ) => {
59
61
this . fire ( e ) ;
60
62
} ) ;
63
+ this . onProgress ( ( event ) => {
64
+ this . refreshStageDeferreds . get ( event . stage ) ?. resolve ( ) ;
65
+ this . refreshStageDeferreds . delete ( event . stage ) ;
66
+ } ) ;
61
67
}
62
68
63
69
public async resolveEnv ( path : string ) : Promise < PythonEnvInfo | undefined > {
@@ -81,7 +87,7 @@ export class EnvsCollectionService extends PythonEnvsWatcher<PythonEnvCollection
81
87
82
88
public getEnvs ( query ?: PythonLocatorQuery ) : PythonEnvInfo [ ] {
83
89
const cachedEnvs = this . cache . getAllEnvs ( ) ;
84
- if ( cachedEnvs . length === 0 && this . refreshPromises . size === 0 ) {
90
+ if ( cachedEnvs . length === 0 && this . refreshDeferreds . size === 0 ) {
85
91
// We expect a refresh to already be triggered when activating discovery component.
86
92
traceError ( 'No python is installed or a refresh has not already been triggered' ) ;
87
93
this . triggerRefresh ( ) . ignoreErrors ( ) ;
@@ -98,20 +104,18 @@ export class EnvsCollectionService extends PythonEnvsWatcher<PythonEnvCollection
98
104
}
99
105
100
106
private startRefresh ( query : ( PythonLocatorQuery & { clearCache ?: boolean } ) | undefined ) : Promise < void > {
101
- const deferred = createDeferred < void > ( ) ;
102
107
if ( query ?. clearCache ) {
103
108
this . cache . clearCache ( ) ;
104
109
}
105
- // Ensure we set this before we trigger the promise to accurately track when a refresh has started.
106
- this . refreshPromises . set ( query , deferred . promise ) ;
110
+ this . createProgressStates ( query ) ;
107
111
const promise = this . addEnvsToCacheForQuery ( query ) ;
108
112
return promise
109
113
. then ( async ( ) => {
110
- // Ensure we delete this before we resolve the promise to accurately track when a refresh finishes.
111
- this . refreshPromises . delete ( query ) ;
112
- deferred . resolve ( ) ;
114
+ this . resolveProgressStates ( query ) ;
113
115
} )
114
- . catch ( ( ex ) => deferred . reject ( ex ) ) ;
116
+ . catch ( ( ex ) => {
117
+ this . rejectProgressStates ( query , ex ) ;
118
+ } ) ;
115
119
}
116
120
117
121
private async addEnvsToCacheForQuery ( query : PythonLocatorQuery | undefined ) {
@@ -126,11 +130,19 @@ export class EnvsCollectionService extends PythonEnvsWatcher<PythonEnvCollection
126
130
if ( iterator . onUpdated !== undefined ) {
127
131
const listener = iterator . onUpdated ( async ( event ) => {
128
132
if ( isProgressEvent ( event ) ) {
129
- if ( event . stage === ProgressReportStage . discoveryFinished ) {
130
- state . done = true ;
131
- listener . dispose ( ) ;
132
- } else if ( event . stage === ProgressReportStage . allPathsDiscovered && ! query ) {
133
- this . progress . fire ( event ) ;
133
+ switch ( event . stage ) {
134
+ case ProgressReportStage . discoveryFinished :
135
+ state . done = true ;
136
+ listener . dispose ( ) ;
137
+ break ;
138
+ case ProgressReportStage . allPathsDiscovered :
139
+ if ( ! query ) {
140
+ // Only mark as all paths discovered when querying for all envs.
141
+ this . progress . fire ( event ) ;
142
+ }
143
+ break ;
144
+ default :
145
+ this . progress . fire ( event ) ;
134
146
}
135
147
} else {
136
148
state . pending += 1 ;
@@ -166,7 +178,7 @@ export class EnvsCollectionService extends PythonEnvsWatcher<PythonEnvCollection
166
178
// Even if no refresh is running for this exact query, there might be other
167
179
// refreshes running for a superset of this query. For eg. the `undefined` query
168
180
// is a superset for every other query, only consider that for simplicity.
169
- return this . refreshPromises . get ( query ) ?? this . refreshPromises . get ( undefined ) ;
181
+ return this . refreshDeferreds . get ( query ) ?. promise ?? this . refreshDeferreds . get ( undefined ) ?. promise ;
170
182
}
171
183
172
184
/**
@@ -187,4 +199,42 @@ export class EnvsCollectionService extends PythonEnvsWatcher<PythonEnvCollection
187
199
}
188
200
return nextRefreshPromise ;
189
201
}
202
+
203
+ private createProgressStates ( query : PythonLocatorQuery | undefined ) {
204
+ this . refreshDeferreds . set ( query , createDeferred < void > ( ) ) ;
205
+ Object . values ( ProgressReportStage ) . forEach ( ( stage ) => {
206
+ const deferred = createDeferred < void > ( ) ;
207
+ this . refreshStageDeferreds . set ( stage , deferred ) ;
208
+ } ) ;
209
+ if ( ProgressReportStage . allPathsDiscovered && query ) {
210
+ // This stage is only applicable when no scope is provided.
211
+ this . refreshStageDeferreds . delete ( ProgressReportStage . allPathsDiscovered ) ;
212
+ }
213
+ }
214
+
215
+ private rejectProgressStates ( query : PythonLocatorQuery | undefined , ex : Error ) {
216
+ this . refreshDeferreds . get ( query ) ?. reject ( ex ) ;
217
+ this . refreshDeferreds . delete ( query ) ;
218
+ Object . values ( ProgressReportStage ) . forEach ( ( stage ) => {
219
+ this . refreshStageDeferreds . get ( stage ) ?. reject ( ex ) ;
220
+ this . refreshStageDeferreds . delete ( stage ) ;
221
+ } ) ;
222
+ }
223
+
224
+ private resolveProgressStates ( query : PythonLocatorQuery | undefined ) {
225
+ this . refreshDeferreds . get ( query ) ?. resolve ( ) ;
226
+ this . refreshDeferreds . delete ( query ) ;
227
+ Object . values ( ProgressReportStage ) . forEach ( ( stage ) => {
228
+ this . refreshStageDeferreds . get ( stage ) ?. resolve ( ) ;
229
+ this . refreshStageDeferreds . delete ( stage ) ;
230
+ } ) ;
231
+ this . checkIfFinishedAndNotify ( ) ;
232
+ }
233
+
234
+ private checkIfFinishedAndNotify ( ) {
235
+ const isRefreshComplete = Array . from ( this . refreshDeferreds . values ( ) ) . every ( ( d ) => d . completed ) ;
236
+ if ( isRefreshComplete ) {
237
+ this . progress . fire ( { stage : ProgressReportStage . discoveryFinished } ) ;
238
+ }
239
+ }
190
240
}
0 commit comments