@@ -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,17 +93,17 @@ 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 ) ;
100
104
this . _initialized . resolve ( ) ;
101
- } else {
102
- throw error ;
103
105
}
106
+ throw error ;
104
107
}
105
108
}
106
109
} ) ;
@@ -114,41 +117,6 @@ export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Cl
114
117
} ) ;
115
118
}
116
119
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
120
protected async createClient (
153
121
port : string | number
154
122
) : Promise < CoreClientProvider . Client > {
@@ -192,7 +160,7 @@ export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Cl
192
160
initReq . setInstance ( instance ) ;
193
161
return new Promise < void > ( ( resolve , reject ) => {
194
162
const stream = client . init ( initReq ) ;
195
- const errorStatus : RpcStatus [ ] = [ ] ;
163
+ const errors : RpcStatus [ ] = [ ] ;
196
164
stream . on ( 'data' , ( res : InitResponse ) => {
197
165
const progress = res . getInitProgress ( ) ;
198
166
if ( progress ) {
@@ -210,28 +178,30 @@ export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Cl
210
178
211
179
const error = res . getError ( ) ;
212
180
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 ) ;
218
186
}
219
187
} ) ;
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 ( ) ;
228
196
} ) ;
229
- stream . on ( 'end' , ( ) =>
230
- errorStatus . length ? reject ( errorStatus ) : resolve ( )
231
- ) ;
232
197
} ) ;
233
198
}
234
199
200
+ private evaluateErrorResponses ( status : RpcStatus [ ] ) : Error | undefined {
201
+ const error = isIndexUpdateRequiredBeforeInit ( status ) ; // put future error matching here
202
+ return error ;
203
+ }
204
+
235
205
protected async updateIndexes (
236
206
client : CoreClientProvider . Client
237
207
) : Promise < CoreClientProvider . Client > {
@@ -338,3 +308,58 @@ export abstract class CoreClientAware {
338
308
) ;
339
309
}
340
310
}
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