@@ -93,10 +93,6 @@ export class CoreClientProvider {
9393 */
9494 private async create ( port : string ) : Promise < CoreClientProvider . Client > {
9595 this . closeClient ( ) ;
96- // Normal startup workflow:
97- // 1. create instance,
98- // 2. init instance,
99- // 3. update indexes asynchronously.
10096 const address = this . address ( port ) ;
10197 const client = await this . createClient ( address ) ;
10298 this . toDisposeBeforeCreate . pushAll ( [
@@ -110,33 +106,46 @@ export class CoreClientProvider {
110106 this . ready = new Deferred ( ) ;
111107 } ) ,
112108 ] ) ;
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 > {
113130 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 ) {
119134 console . error (
120135 'The primary packages indexes are missing. Running indexes update before initializing the core gRPC client' ,
121- error . message
136+ err . message
122137 ) ;
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
129139 await this . initInstance ( client ) ;
130140 console . info (
131141 `Downloaded the primary packages indexes, and successfully initialized the core gRPC client.`
132142 ) ;
133- return this . useClient ( client ) ;
134143 } else {
135144 console . error (
136145 'Error occurred while initializing the core gRPC client provider' ,
137- error
146+ err
138147 ) ;
139- throw error ;
148+ throw err ;
140149 }
141150 }
142151 }
@@ -195,44 +204,31 @@ export class CoreClientProvider {
195204 client,
196205 instance,
197206 } : CoreClientProvider . Client ) : Promise < void > {
198- const initReq = new InitRequest ( ) ;
199- initReq . setInstance ( instance ) ;
200207 return new Promise < void > ( ( resolve , reject ) => {
201- const stream = client . init ( initReq ) ;
202208 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 ) ;
210221 }
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 ;
215229 }
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+ } ) ;
236232 } ) ;
237233 }
238234
@@ -251,32 +247,34 @@ export class CoreClientProvider {
251247 private async refreshIndexes ( ) : Promise < void > {
252248 const client = this . _client ;
253249 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+ }
256262 }
257263 }
258264
259265 private async updateIndexes (
260- client : CoreClientProvider . Client
266+ client : CoreClientProvider . Client ,
267+ progressHandler ?: IndexesUpdateProgressHandler
261268 ) : 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+ ] ) ;
275273 }
276274
277275 private async updateIndex (
278276 client : CoreClientProvider . Client ,
279- progressHandler : IndexesUpdateProgressHandler
277+ progressHandler ? : IndexesUpdateProgressHandler
280278 ) : Promise < void > {
281279 return this . doUpdateIndex (
282280 ( ) =>
@@ -290,7 +288,7 @@ export class CoreClientProvider {
290288
291289 private async updateLibraryIndex (
292290 client : CoreClientProvider . Client ,
293- progressHandler : IndexesUpdateProgressHandler
291+ progressHandler ? : IndexesUpdateProgressHandler
294292 ) : Promise < void > {
295293 return this . doUpdateIndex (
296294 ( ) =>
@@ -309,10 +307,10 @@ export class CoreClientProvider {
309307 | UpdateCoreLibrariesIndexResponse // not used by IDE2
310308 > (
311309 responseProvider : ( ) => grpc . ClientReadableStream < R > ,
312- progressHandler : IndexesUpdateProgressHandler ,
310+ progressHandler ? : IndexesUpdateProgressHandler ,
313311 task ?: string
314312 ) : Promise < void > {
315- const { progressId } = progressHandler ;
313+ const progressId = progressHandler ?. progressId ;
316314 return retry (
317315 ( ) =>
318316 new Promise < void > ( ( resolve , reject ) => {
@@ -326,7 +324,7 @@ export class CoreClientProvider {
326324 `core-client-provider${ task ? ` [${ task } ]` : '' } ` ,
327325 message
328326 ) ;
329- progressHandler . reportProgress ( message ) ;
327+ progressHandler ? .reportProgress ( message ) ;
330328 } ,
331329 } ,
332330 progressId,
0 commit comments