@@ -21,7 +21,10 @@ import {
2121import * as commandsGrpcPb from './cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb' ;
2222import { NotificationServiceServer } from '../common/protocol' ;
2323import { Deferred , retry } from '@theia/core/lib/common/promise-util' ;
24- import { Status as RpcStatus } from './cli-protocol/google/rpc/status_pb' ;
24+ import {
25+ Status as RpcStatus ,
26+ Status ,
27+ } from './cli-protocol/google/rpc/status_pb' ;
2528
2629@injectable ( )
2730export class CoreClientProvider extends GrpcClientProvider < CoreClientProvider . Client > {
@@ -90,10 +93,11 @@ export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Cl
9093 this . _initialized . resolve ( ) ;
9194 this . updateIndex ( this . _client ) ; // Update the indexes asynchronously
9295 } catch ( error : unknown ) {
93- if (
94- this . isPackageIndexMissingError ( error ) ||
95- this . isDiscoveryNotFoundError ( error )
96- ) {
96+ console . error (
97+ 'Error occurred while initializing the core gRPC client provider' ,
98+ error
99+ ) ;
100+ if ( error instanceof IndexUpdateRequiredBeforeInitError ) {
97101 // If it's a first start, IDE2 must run index update before the init request.
98102 await this . updateIndexes ( this . _client ) ;
99103 await this . initInstance ( this . _client ) ;
@@ -114,41 +118,6 @@ export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Cl
114118 } ) ;
115119 }
116120
117- private isPackageIndexMissingError ( error : unknown ) : boolean {
118- const assert = ( message : string ) =>
119- message . includes ( 'loading json index file' ) ;
120- // https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/package_manager.go#L247
121- return this . isRpcStatusError ( error , assert ) ;
122- }
123-
124- private isDiscoveryNotFoundError ( error : unknown ) : boolean {
125- const assert = ( message : string ) =>
126- message . includes ( 'discovery' ) &&
127- ( message . includes ( 'not found' ) || message . includes ( 'not installed' ) ) ;
128- // https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/loader.go#L740
129- // https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/loader.go#L744
130- return this . isRpcStatusError ( error , assert ) ;
131- }
132-
133- private isCancelError ( error : unknown ) : boolean {
134- return (
135- error instanceof Error &&
136- error . message . toLocaleLowerCase ( ) . includes ( 'cancelled on client' )
137- ) ;
138- }
139-
140- // Final error codes are not yet defined by the CLI. Hence, we do string matching in the message RPC status.
141- private isRpcStatusError (
142- error : unknown ,
143- assert : ( message : string ) => boolean
144- ) {
145- if ( error instanceof RpcStatus ) {
146- const { message } = RpcStatus . toObject ( false , error ) ;
147- return assert ( message . toLocaleLowerCase ( ) ) ;
148- }
149- return false ;
150- }
151-
152121 protected async createClient (
153122 port : string | number
154123 ) : Promise < CoreClientProvider . Client > {
@@ -192,7 +161,7 @@ export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Cl
192161 initReq . setInstance ( instance ) ;
193162 return new Promise < void > ( ( resolve , reject ) => {
194163 const stream = client . init ( initReq ) ;
195- const errorStatus : RpcStatus [ ] = [ ] ;
164+ const errors : RpcStatus [ ] = [ ] ;
196165 stream . on ( 'data' , ( res : InitResponse ) => {
197166 const progress = res . getInitProgress ( ) ;
198167 if ( progress ) {
@@ -210,28 +179,30 @@ export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Cl
210179
211180 const error = res . getError ( ) ;
212181 if ( error ) {
213- console . error ( error . getMessage ( ) ) ;
214- errorStatus . push ( error ) ;
215- // Cancel the init request. No need to wait until the end of the event. The init has already failed.
216- // Canceling the request will result in a cancel error, but we need to reject with the original error later.
217- stream . cancel ( ) ;
182+ const { code , message } = Status . toObject ( false , error ) ;
183+ console . error (
184+ `Detected an error response during the gRPC core client initialization: code: ${ code } , message: ${ message } `
185+ ) ;
186+ errors . push ( error ) ;
218187 }
219188 } ) ;
220- stream . on ( 'error' , ( error ) => {
221- // On any error during the init request, the request is canceled.
222- // On cancel, the IDE2 ignores the cancel error and rejects with the original one.
223- reject (
224- this . isCancelError ( error ) && errorStatus . length
225- ? errorStatus [ 0 ]
226- : error
227- ) ;
189+ stream . on ( 'error' , reject ) ;
190+ stream . on ( 'end' , ( ) => {
191+ const error = this . evaluateErrorStatus ( errors ) ;
192+ if ( error ) {
193+ reject ( error ) ;
194+ return ;
195+ }
196+ resolve ( ) ;
228197 } ) ;
229- stream . on ( 'end' , ( ) =>
230- errorStatus . length ? reject ( errorStatus ) : resolve ( )
231- ) ;
232198 } ) ;
233199 }
234200
201+ private evaluateErrorStatus ( status : RpcStatus [ ] ) : Error | undefined {
202+ const error = isIndexUpdateRequiredBeforeInit ( status ) ; // put future error matching here
203+ return error ;
204+ }
205+
235206 protected async updateIndexes (
236207 client : CoreClientProvider . Client
237208 ) : Promise < CoreClientProvider . Client > {
@@ -338,3 +309,58 @@ export abstract class CoreClientAware {
338309 ) ;
339310 }
340311}
312+
313+ class IndexUpdateRequiredBeforeInitError extends Error {
314+ constructor ( causes : RpcStatus . AsObject [ ] ) {
315+ super ( `The index of the cores and libraries must be updated before initializing the core gRPC client.
316+ The following problems were detected during the gRPC client initialization:
317+ ${ causes
318+ . map ( ( { code, message } ) => ` - code: ${ code } , message: ${ message } ` )
319+ . join ( '\n' ) }
320+ ` ) ;
321+ Object . setPrototypeOf ( this , IndexUpdateRequiredBeforeInitError . prototype ) ;
322+ if ( ! causes . length ) {
323+ throw new Error ( `expected non-empty 'causes'` ) ;
324+ }
325+ }
326+ }
327+
328+ function isIndexUpdateRequiredBeforeInit (
329+ status : RpcStatus [ ]
330+ ) : IndexUpdateRequiredBeforeInitError | undefined {
331+ const causes = status
332+ . filter ( ( s ) =>
333+ IndexUpdateRequiredBeforeInit . map ( ( predicate ) => predicate ( s ) ) . some (
334+ Boolean
335+ )
336+ )
337+ . map ( ( s ) => RpcStatus . toObject ( false , s ) ) ;
338+ return causes . length
339+ ? new IndexUpdateRequiredBeforeInitError ( causes )
340+ : undefined ;
341+ }
342+ const IndexUpdateRequiredBeforeInit = [
343+ isPackageIndexMissingStatus ,
344+ isDiscoveryNotFoundStatus ,
345+ ] ;
346+ function isPackageIndexMissingStatus ( status : RpcStatus ) : boolean {
347+ const predicate = ( { message } : RpcStatus . AsObject ) =>
348+ message . includes ( 'loading json index file' ) ;
349+ // https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/package_manager.go#L247
350+ return evaluate ( status , predicate ) ;
351+ }
352+ function isDiscoveryNotFoundStatus ( status : RpcStatus ) : boolean {
353+ const predicate = ( { message } : RpcStatus . AsObject ) =>
354+ message . includes ( 'discovery' ) &&
355+ ( message . includes ( 'not found' ) || message . includes ( 'not installed' ) ) ;
356+ // https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/loader.go#L740
357+ // https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/loader.go#L744
358+ return evaluate ( status , predicate ) ;
359+ }
360+ function evaluate (
361+ subject : RpcStatus ,
362+ predicate : ( error : RpcStatus . AsObject ) => boolean
363+ ) : boolean {
364+ const status = RpcStatus . toObject ( false , subject ) ;
365+ return predicate ( status ) ;
366+ }
0 commit comments