@@ -93,10 +93,6 @@ export class CoreClientProvider {
93
93
*/
94
94
private async create ( port : string ) : Promise < CoreClientProvider . Client > {
95
95
this . closeClient ( ) ;
96
- // Normal startup workflow:
97
- // 1. create instance,
98
- // 2. init instance,
99
- // 3. update indexes asynchronously.
100
96
const address = this . address ( port ) ;
101
97
const client = await this . createClient ( address ) ;
102
98
this . toDisposeBeforeCreate . pushAll ( [
@@ -110,33 +106,46 @@ export class CoreClientProvider {
110
106
this . ready = new Deferred ( ) ;
111
107
} ) ,
112
108
] ) ;
109
+ await this . initInstanceWithFallback ( client ) ;
110
+ setTimeout ( async ( ) => this . refreshIndexes ( ) , 10_000 ) ; // Update the indexes asynchronously
111
+ return this . useClient ( client ) ;
112
+ }
113
+
114
+ /**
115
+ * By default, calling this method is equivalent to the `initInstance(Client)` call.
116
+ * When the IDE2 starts and one of the followings is missing,
117
+ * the IDE2 must run the index update before the core client initialization:
118
+ *
119
+ * - primary package index (`#directories.data/package_index.json`),
120
+ * - library index (`#directories.data/library_index.json`),
121
+ * - built-in tools (`builtin:serial-discovery` or `builtin:mdns-discovery`)
122
+ *
123
+ * This method detects such errors and runs an index update before initializing the client.
124
+ * The index update will fail if the 3rd URLs list contains an invalid URL,
125
+ * and the IDE2 will be [non-functional](https://github.com/arduino/arduino-ide/issues/1084). Since the CLI [cannot update only the primary package index]((https://github.com/arduino/arduino-cli/issues/1788)), IDE2 does its dirty solution.
126
+ */
127
+ private async initInstanceWithFallback (
128
+ client : CoreClientProvider . Client
129
+ ) : Promise < void > {
113
130
try {
114
- await this . initInstance ( client ) ; // init the gRPC core client instance
115
- setTimeout ( async ( ) => this . refreshIndexes ( ) , 10_000 ) ; // Update the indexes asynchronously
116
- return this . useClient ( client ) ;
117
- } catch ( error ) {
118
- if ( error instanceof IndexUpdateRequiredBeforeInitError ) {
131
+ await this . initInstance ( client ) ;
132
+ } catch ( err ) {
133
+ if ( err instanceof IndexUpdateRequiredBeforeInitError ) {
119
134
console . error (
120
135
'The primary packages indexes are missing. Running indexes update before initializing the core gRPC client' ,
121
- error . message
136
+ err . message
122
137
) ;
123
- // If it's a first start, IDE2 must run index update before the init request.
124
- // First startup workflow:
125
- // 1. create instance,
126
- // 2. update indexes and wait (to download the built-in pluggable tools, etc),
127
- // 3. init instance.
128
- await this . updateIndexes ( client ) ;
138
+ await this . updateIndexes ( client ) ; // TODO: this should run without the 3rd party URLs
129
139
await this . initInstance ( client ) ;
130
140
console . info (
131
141
`Downloaded the primary packages indexes, and successfully initialized the core gRPC client.`
132
142
) ;
133
- return this . useClient ( client ) ;
134
143
} else {
135
144
console . error (
136
145
'Error occurred while initializing the core gRPC client provider' ,
137
- error
146
+ err
138
147
) ;
139
- throw error ;
148
+ throw err ;
140
149
}
141
150
}
142
151
}
@@ -195,44 +204,31 @@ export class CoreClientProvider {
195
204
client,
196
205
instance,
197
206
} : CoreClientProvider . Client ) : Promise < void > {
198
- const initReq = new InitRequest ( ) ;
199
- initReq . setInstance ( instance ) ;
200
207
return new Promise < void > ( ( resolve , reject ) => {
201
- const stream = client . init ( initReq ) ;
202
208
const errors : RpcStatus [ ] = [ ] ;
203
- stream . on ( 'data' , ( res : InitResponse ) => {
204
- const progress = res . getInitProgress ( ) ;
205
- if ( progress ) {
206
- const downloadProgress = progress . getDownloadProgress ( ) ;
207
- if ( downloadProgress && downloadProgress . getCompleted ( ) ) {
208
- const file = downloadProgress . getFile ( ) ;
209
- console . log ( `Downloaded ${ file } ` ) ;
209
+ client
210
+ . init ( new InitRequest ( ) . setInstance ( instance ) )
211
+ . on ( 'data' , ( resp : InitResponse ) => {
212
+ // The CLI never sends `initProgress`, it's always `error` or nothing. Is this a CLI bug?
213
+ // According to the gRPC API, the CLI should send either a `TaskProgress` or a `DownloadProgress`, but it does not.
214
+ const error = resp . getError ( ) ;
215
+ if ( error ) {
216
+ const { code, message } = Status . toObject ( false , error ) ;
217
+ console . error (
218
+ `Detected an error response during the gRPC core client initialization: code: ${ code } , message: ${ message } `
219
+ ) ;
220
+ errors . push ( error ) ;
210
221
}
211
- const taskProgress = progress . getTaskProgress ( ) ;
212
- if ( taskProgress && taskProgress . getCompleted ( ) ) {
213
- const name = taskProgress . getName ( ) ;
214
- console . log ( `Completed ${ name } ` ) ;
222
+ } )
223
+ . on ( 'error' , reject )
224
+ . on ( 'end' , ( ) => {
225
+ const error = this . evaluateErrorStatus ( errors ) ;
226
+ if ( error ) {
227
+ reject ( error ) ;
228
+ return ;
215
229
}
216
- }
217
-
218
- const error = res . getError ( ) ;
219
- if ( error ) {
220
- const { code, message } = Status . toObject ( false , error ) ;
221
- console . error (
222
- `Detected an error response during the gRPC core client initialization: code: ${ code } , message: ${ message } `
223
- ) ;
224
- errors . push ( error ) ;
225
- }
226
- } ) ;
227
- stream . on ( 'error' , reject ) ;
228
- stream . on ( 'end' , ( ) => {
229
- const error = this . evaluateErrorStatus ( errors ) ;
230
- if ( error ) {
231
- reject ( error ) ;
232
- return ;
233
- }
234
- resolve ( ) ;
235
- } ) ;
230
+ resolve ( ) ;
231
+ } ) ;
236
232
} ) ;
237
233
}
238
234
@@ -251,32 +247,34 @@ export class CoreClientProvider {
251
247
private async refreshIndexes ( ) : Promise < void > {
252
248
const client = this . _client ;
253
249
if ( client ) {
254
- await this . updateIndexes ( client ) ;
255
- await this . initInstance ( client ) ;
250
+ const progressHandler = this . createProgressHandler ( ) ;
251
+ try {
252
+ await this . updateIndexes ( client , progressHandler ) ;
253
+ await this . initInstance ( client ) ;
254
+ // notify clients about the index update only after the client has been "re-initialized" and the new content is available.
255
+ progressHandler . reportEnd ( ) ;
256
+ } catch ( err ) {
257
+ console . error ( 'Failed to update indexes' , err ) ;
258
+ progressHandler . reportError (
259
+ ServiceError . is ( err ) ? err . details : String ( err )
260
+ ) ;
261
+ }
256
262
}
257
263
}
258
264
259
265
private async updateIndexes (
260
- client : CoreClientProvider . Client
266
+ client : CoreClientProvider . Client ,
267
+ progressHandler ?: IndexesUpdateProgressHandler
261
268
) : Promise < void > {
262
- const progressHandler = this . createProgressHandler ( ) ;
263
- try {
264
- await Promise . all ( [
265
- this . updateIndex ( client , progressHandler ) ,
266
- this . updateLibraryIndex ( client , progressHandler ) ,
267
- ] ) ;
268
- progressHandler . reportEnd ( ) ;
269
- } catch ( err ) {
270
- console . error ( 'Failed to update indexes' , err ) ;
271
- progressHandler . reportError (
272
- ServiceError . is ( err ) ? err . details : String ( err )
273
- ) ;
274
- }
269
+ await Promise . all ( [
270
+ this . updateIndex ( client , progressHandler ) ,
271
+ this . updateLibraryIndex ( client , progressHandler ) ,
272
+ ] ) ;
275
273
}
276
274
277
275
private async updateIndex (
278
276
client : CoreClientProvider . Client ,
279
- progressHandler : IndexesUpdateProgressHandler
277
+ progressHandler ? : IndexesUpdateProgressHandler
280
278
) : Promise < void > {
281
279
return this . doUpdateIndex (
282
280
( ) =>
@@ -290,7 +288,7 @@ export class CoreClientProvider {
290
288
291
289
private async updateLibraryIndex (
292
290
client : CoreClientProvider . Client ,
293
- progressHandler : IndexesUpdateProgressHandler
291
+ progressHandler ? : IndexesUpdateProgressHandler
294
292
) : Promise < void > {
295
293
return this . doUpdateIndex (
296
294
( ) =>
@@ -309,10 +307,10 @@ export class CoreClientProvider {
309
307
| UpdateCoreLibrariesIndexResponse // not used by IDE2
310
308
> (
311
309
responseProvider : ( ) => grpc . ClientReadableStream < R > ,
312
- progressHandler : IndexesUpdateProgressHandler ,
310
+ progressHandler ? : IndexesUpdateProgressHandler ,
313
311
task ?: string
314
312
) : Promise < void > {
315
- const { progressId } = progressHandler ;
313
+ const progressId = progressHandler ?. progressId ;
316
314
return retry (
317
315
( ) =>
318
316
new Promise < void > ( ( resolve , reject ) => {
@@ -326,7 +324,7 @@ export class CoreClientProvider {
326
324
`core-client-provider${ task ? ` [${ task } ]` : '' } ` ,
327
325
message
328
326
) ;
329
- progressHandler . reportProgress ( message ) ;
327
+ progressHandler ? .reportProgress ( message ) ;
330
328
} ,
331
329
} ,
332
330
progressId,
0 commit comments