@@ -21,7 +21,10 @@ import {
21
21
import * as commandsGrpcPb from './cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb' ;
22
22
import { NotificationServiceServer } from '../common/protocol' ;
23
23
import { 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' ;
25
28
26
29
@injectable ( )
27
30
export class CoreClientProvider extends GrpcClientProvider < CoreClientProvider . Client > {
@@ -90,10 +93,11 @@ export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Cl
90
93
this . _initialized . resolve ( ) ;
91
94
this . updateIndex ( this . _client ) ; // Update the indexes asynchronously
92
95
} 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 ) {
97
101
// If it's a first start, IDE2 must run index update before the init request.
98
102
await this . updateIndexes ( this . _client ) ;
99
103
await this . initInstance ( this . _client ) ;
@@ -114,41 +118,6 @@ export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Cl
114
118
} ) ;
115
119
}
116
120
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
-
152
121
protected async createClient (
153
122
port : string | number
154
123
) : Promise < CoreClientProvider . Client > {
@@ -192,7 +161,7 @@ export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Cl
192
161
initReq . setInstance ( instance ) ;
193
162
return new Promise < void > ( ( resolve , reject ) => {
194
163
const stream = client . init ( initReq ) ;
195
- const errorStatus : RpcStatus [ ] = [ ] ;
164
+ const errors : RpcStatus [ ] = [ ] ;
196
165
stream . on ( 'data' , ( res : InitResponse ) => {
197
166
const progress = res . getInitProgress ( ) ;
198
167
if ( progress ) {
@@ -210,28 +179,30 @@ export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Cl
210
179
211
180
const error = res . getError ( ) ;
212
181
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 ) ;
218
187
}
219
188
} ) ;
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 ( ) ;
228
197
} ) ;
229
- stream . on ( 'end' , ( ) =>
230
- errorStatus . length ? reject ( errorStatus ) : resolve ( )
231
- ) ;
232
198
} ) ;
233
199
}
234
200
201
+ private evaluateErrorStatus ( status : RpcStatus [ ] ) : Error | undefined {
202
+ const error = isIndexUpdateRequiredBeforeInit ( status ) ; // put future error matching here
203
+ return error ;
204
+ }
205
+
235
206
protected async updateIndexes (
236
207
client : CoreClientProvider . Client
237
208
) : Promise < CoreClientProvider . Client > {
@@ -338,3 +309,58 @@ export abstract class CoreClientAware {
338
309
) ;
339
310
}
340
311
}
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