@@ -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,17 +93,17 @@ 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 ) ;
100104 this . _initialized . resolve ( ) ;
101- } else {
102- throw error ;
103105 }
106+ throw error ;
104107 }
105108 }
106109 } ) ;
@@ -114,41 +117,6 @@ export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Cl
114117 } ) ;
115118 }
116119
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-
152120 protected async createClient (
153121 port : string | number
154122 ) : Promise < CoreClientProvider . Client > {
@@ -192,7 +160,7 @@ export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Cl
192160 initReq . setInstance ( instance ) ;
193161 return new Promise < void > ( ( resolve , reject ) => {
194162 const stream = client . init ( initReq ) ;
195- const errorStatus : RpcStatus [ ] = [ ] ;
163+ const errors : RpcStatus [ ] = [ ] ;
196164 stream . on ( 'data' , ( res : InitResponse ) => {
197165 const progress = res . getInitProgress ( ) ;
198166 if ( progress ) {
@@ -210,28 +178,30 @@ export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Cl
210178
211179 const error = res . getError ( ) ;
212180 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 ( ) ;
181+ const { code , message } = Status . toObject ( false , error ) ;
182+ console . error (
183+ `Detected an error response during the gRPC core client initialization: code: ${ code } , message: ${ message } `
184+ ) ;
185+ errors . push ( error ) ;
218186 }
219187 } ) ;
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- ) ;
188+ stream . on ( 'error' , reject ) ;
189+ stream . on ( 'end' , ( ) => {
190+ const error = this . evaluateErrorResponses ( errors ) ;
191+ if ( error ) {
192+ reject ( error ) ;
193+ return ;
194+ }
195+ resolve ( ) ;
228196 } ) ;
229- stream . on ( 'end' , ( ) =>
230- errorStatus . length ? reject ( errorStatus ) : resolve ( )
231- ) ;
232197 } ) ;
233198 }
234199
200+ private evaluateErrorResponses ( status : RpcStatus [ ] ) : Error | undefined {
201+ const error = isIndexUpdateRequiredBeforeInit ( status ) ; // put future error matching here
202+ return error ;
203+ }
204+
235205 protected async updateIndexes (
236206 client : CoreClientProvider . Client
237207 ) : Promise < CoreClientProvider . Client > {
@@ -338,3 +308,58 @@ export abstract class CoreClientAware {
338308 ) ;
339309 }
340310}
311+
312+ class IndexUpdateRequiredBeforeInitError extends Error {
313+ constructor ( causes : RpcStatus . AsObject [ ] ) {
314+ super ( `The index of the cores and libraries must be updated before initializing the core gRPC client.
315+ The following were detected during the gRPC client initialization:
316+ ${ causes
317+ . map ( ( { code, message } ) => ` - code: ${ code } , message: ${ message } ` )
318+ . join ( '\n' ) }
319+ ` ) ;
320+ Object . setPrototypeOf ( this , IndexUpdateRequiredBeforeInitError . prototype ) ;
321+ if ( ! causes . length ) {
322+ throw new Error ( `expected non-empty 'causes'` ) ;
323+ }
324+ }
325+ }
326+
327+ function isIndexUpdateRequiredBeforeInit (
328+ status : RpcStatus [ ]
329+ ) : IndexUpdateRequiredBeforeInitError | undefined {
330+ const causes = status
331+ . filter ( ( s ) =>
332+ IndexUpdateRequiredBeforeInit . map ( ( predicate ) => predicate ( s ) ) . some (
333+ Boolean
334+ )
335+ )
336+ . map ( ( s ) => RpcStatus . toObject ( false , s ) ) ;
337+ return causes . length
338+ ? new IndexUpdateRequiredBeforeInitError ( causes )
339+ : undefined ;
340+ }
341+ const IndexUpdateRequiredBeforeInit = [
342+ isPackageIndexMissingStatus ,
343+ isDiscoveryNotFoundStatus ,
344+ ] ;
345+ function isPackageIndexMissingStatus ( status : RpcStatus ) : boolean {
346+ const predicate = ( { message } : RpcStatus . AsObject ) =>
347+ message . includes ( 'loading json index file' ) ;
348+ // https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/package_manager.go#L247
349+ return evaluate ( status , predicate ) ;
350+ }
351+ function isDiscoveryNotFoundStatus ( status : RpcStatus ) : boolean {
352+ const predicate = ( { message } : RpcStatus . AsObject ) =>
353+ message . includes ( 'discovery' ) &&
354+ ( message . includes ( 'not found' ) || message . includes ( 'not installed' ) ) ;
355+ // https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/loader.go#L740
356+ // https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/loader.go#L744
357+ return evaluate ( status , predicate ) ;
358+ }
359+ function evaluate (
360+ subject : RpcStatus ,
361+ predicate : ( error : RpcStatus . AsObject ) => boolean
362+ ) : boolean {
363+ const status = RpcStatus . toObject ( false , subject ) ;
364+ return predicate ( status ) ;
365+ }
0 commit comments