1
- import { injectable , inject , named } from '@theia/core/shared/inversify' ;
2
1
import { ClientDuplexStream } from '@grpc/grpc-js' ;
2
+ import { DisposableCollection } from '@theia/core/lib/common/disposable' ;
3
+ import { Emitter , Event } from '@theia/core/lib/common/event' ;
3
4
import { ILogger } from '@theia/core/lib/common/logger' ;
4
5
import { deepClone } from '@theia/core/lib/common/objects' ;
5
- import { CoreClientAware } from './core-client-provider' ;
6
- import {
7
- BoardListWatchRequest ,
8
- BoardListWatchResponse ,
9
- } from './cli-protocol/cc/arduino/cli/commands/v1/board_pb' ;
6
+ import { Deferred } from '@theia/core/lib/common/promise-util' ;
7
+ import { BackendApplicationContribution } from '@theia/core/lib/node' ;
8
+ import { inject , injectable , named } from '@theia/core/shared/inversify' ;
9
+ import { Disposable } from '@theia/core/shared/vscode-languageserver-protocol' ;
10
+ import { v4 } from 'uuid' ;
11
+ import { Unknown } from '../common/nls' ;
10
12
import {
13
+ AttachedBoardsChangeEvent ,
14
+ AvailablePorts ,
11
15
Board ,
12
- Port ,
13
16
NotificationServiceServer ,
14
- AvailablePorts ,
15
- AttachedBoardsChangeEvent ,
17
+ Port ,
16
18
} from '../common/protocol' ;
17
- import { Emitter , Event } from '@theia/core/lib/common/event' ;
18
- import { DisposableCollection } from '@theia/core/lib/common/disposable' ;
19
- import { Disposable } from '@theia/core/shared/vscode-languageserver-protocol' ;
19
+ import {
20
+ BoardListWatchRequest ,
21
+ BoardListWatchResponse ,
22
+ DetectedPort as RpcDetectedPort ,
23
+ } from './cli-protocol/cc/arduino/cli/commands/v1/board_pb' ;
20
24
import { ArduinoCoreServiceClient } from './cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb' ;
21
- import { v4 } from 'uuid' ;
25
+ import { Port as RpcPort } from './cli-protocol/cc/arduino/cli/commands/v1/port_pb' ;
26
+ import { CoreClientAware } from './core-client-provider' ;
22
27
import { ServiceError } from './service-error' ;
23
- import { BackendApplicationContribution } from '@theia/core/lib/node' ;
24
- import { Deferred } from '@theia/core/lib/common/promise-util' ;
25
28
26
29
type Duplex = ClientDuplexStream < BoardListWatchRequest , BoardListWatchResponse > ;
27
30
interface StreamWrapper extends Disposable {
@@ -210,7 +213,7 @@ export class BoardDiscovery
210
213
} else {
211
214
throw new Error ( `Unhandled object type: ${ arg } ` ) ;
212
215
}
213
- return JSON . stringify ( object ) ;
216
+ return JSON . stringify ( object , null , 2 ) ; // TODO: remove `space`?
214
217
}
215
218
216
219
async start ( ) : Promise < void > {
@@ -228,102 +231,7 @@ export class BoardDiscovery
228
231
this . logger . info ( 'start new deferred' ) ;
229
232
const { client, instance } = await this . coreClient ;
230
233
const wrapper = await this . createWrapper ( client ) ;
231
- wrapper . stream . on ( 'data' , async ( resp : BoardListWatchResponse ) => {
232
- this . logger . info ( 'onData' , this . toJson ( resp ) ) ;
233
- if ( resp . getEventType ( ) === 'quit' ) {
234
- this . logger . info ( 'quit received' ) ;
235
- this . stop ( ) ;
236
- return ;
237
- }
238
-
239
- const detectedPort = resp . getPort ( ) ;
240
- if ( detectedPort ) {
241
- let eventType : 'add' | 'remove' | 'unknown' = 'unknown' ;
242
- if ( resp . getEventType ( ) === 'add' ) {
243
- eventType = 'add' ;
244
- } else if ( resp . getEventType ( ) === 'remove' ) {
245
- eventType = 'remove' ;
246
- } else {
247
- eventType = 'unknown' ;
248
- }
249
-
250
- if ( eventType === 'unknown' ) {
251
- throw new Error ( `Unexpected event type: '${ resp . getEventType ( ) } '` ) ;
252
- }
253
-
254
- const oldState = deepClone ( this . _availablePorts ) ;
255
- const newState = deepClone ( this . _availablePorts ) ;
256
-
257
- const address = ( detectedPort as any ) . getPort ( ) . getAddress ( ) ;
258
- const protocol = ( detectedPort as any ) . getPort ( ) . getProtocol ( ) ;
259
- // Different discoveries can detect the same port with different
260
- // protocols, so we consider the combination of address and protocol
261
- // to be the id of a certain port to distinguish it from others.
262
- // If we'd use only the address of a port to store it in a map
263
- // we can have conflicts the same port is found with multiple
264
- // protocols.
265
- const portID = `${ address } |${ protocol } ` ;
266
- const label = ( detectedPort as any ) . getPort ( ) . getLabel ( ) ;
267
- const protocolLabel = ( detectedPort as any )
268
- . getPort ( )
269
- . getProtocolLabel ( ) ;
270
- const port = {
271
- id : portID ,
272
- address,
273
- addressLabel : label ,
274
- protocol,
275
- protocolLabel,
276
- } ;
277
- const boards : Board [ ] = [ ] ;
278
- for ( const item of detectedPort . getMatchingBoardsList ( ) ) {
279
- boards . push ( {
280
- fqbn : item . getFqbn ( ) ,
281
- name : item . getName ( ) || 'unknown' ,
282
- port,
283
- } ) ;
284
- }
285
-
286
- if ( eventType === 'add' ) {
287
- if ( newState [ portID ] ) {
288
- const [ , knownBoards ] = newState [ portID ] ;
289
- this . logger . warn (
290
- `Port '${ Port . toString (
291
- port
292
- ) } ' was already available. Known boards before override: ${ JSON . stringify (
293
- knownBoards
294
- ) } `
295
- ) ;
296
- }
297
- newState [ portID ] = [ port , boards ] ;
298
- } else if ( eventType === 'remove' ) {
299
- if ( ! newState [ portID ] ) {
300
- this . logger . warn (
301
- `Port '${ Port . toString ( port ) } ' was not available. Skipping`
302
- ) ;
303
- return ;
304
- }
305
- delete newState [ portID ] ;
306
- }
307
-
308
- const oldAvailablePorts = this . getAvailablePorts ( oldState ) ;
309
- const oldAttachedBoards = this . getAttachedBoards ( oldState ) ;
310
- const newAvailablePorts = this . getAvailablePorts ( newState ) ;
311
- const newAttachedBoards = this . getAttachedBoards ( newState ) ;
312
- const event : AttachedBoardsChangeEvent = {
313
- oldState : {
314
- ports : oldAvailablePorts ,
315
- boards : oldAttachedBoards ,
316
- } ,
317
- newState : {
318
- ports : newAvailablePorts ,
319
- boards : newAttachedBoards ,
320
- } ,
321
- } ;
322
-
323
- this . _availablePorts = newState ;
324
- this . notificationService . notifyAttachedBoardsDidChange ( event ) ;
325
- }
326
- } ) ;
234
+ wrapper . stream . on ( 'data' , ( resp ) => this . onBoardListWatchResponse ( resp ) ) ;
327
235
this . logger . info ( 'start request start watch' ) ;
328
236
await this . requestStartWatch (
329
237
new BoardListWatchRequest ( ) . setInstance ( instance ) ,
@@ -334,21 +242,123 @@ export class BoardDiscovery
334
242
this . logger . info ( 'start resolved watching' ) ;
335
243
}
336
244
337
- getAttachedBoards ( state : AvailablePorts = this . availablePorts ) : Board [ ] {
338
- const attachedBoards : Board [ ] = [ ] ;
339
- for ( const portID of Object . keys ( state ) ) {
340
- const [ , boards ] = state [ portID ] ;
341
- attachedBoards . push ( ...boards ) ;
245
+ // XXX: make this `protected` and override for tests if IDE2 wants to mock events from the CLI.
246
+ private onBoardListWatchResponse ( resp : BoardListWatchResponse ) : void {
247
+ this . logger . info ( this . toJson ( resp ) ) ;
248
+ const eventType = EventType . parse ( resp . getEventType ( ) ) ;
249
+
250
+ if ( eventType === EventType . Quit ) {
251
+ this . logger . info ( 'quit received' ) ;
252
+ this . stop ( ) ;
253
+ return ;
342
254
}
343
- return attachedBoards ;
255
+
256
+ const detectedPort = resp . getPort ( ) ;
257
+ if ( detectedPort ) {
258
+ const { port, boards } = this . fromRpc ( detectedPort ) ;
259
+ if ( ! port ) {
260
+ if ( ! ! boards . length ) {
261
+ console . warn (
262
+ `Could not detect the port, but unexpectedly received discovered boards. This is most likely a bug! Response was: ${ this . toJson (
263
+ resp
264
+ ) } `
265
+ ) ;
266
+ }
267
+ return ;
268
+ }
269
+ const oldState = deepClone ( this . _availablePorts ) ;
270
+ const newState = deepClone ( this . _availablePorts ) ;
271
+ const key = Port . keyOf ( port ) ;
272
+
273
+ if ( eventType === EventType . Add ) {
274
+ if ( newState [ key ] ) {
275
+ const [ , knownBoards ] = newState [ key ] ;
276
+ this . logger . warn (
277
+ `Port '${ Port . toString (
278
+ port
279
+ ) } ' was already available. Known boards before override: ${ JSON . stringify (
280
+ knownBoards
281
+ ) } `
282
+ ) ;
283
+ }
284
+ newState [ key ] = [ port , boards ] ;
285
+ } else if ( eventType === EventType . Remove ) {
286
+ if ( ! newState [ key ] ) {
287
+ this . logger . warn (
288
+ `Port '${ Port . toString ( port ) } ' was not available. Skipping`
289
+ ) ;
290
+ return ;
291
+ }
292
+ delete newState [ key ] ;
293
+ }
294
+
295
+ const event : AttachedBoardsChangeEvent = {
296
+ oldState : {
297
+ ...AvailablePorts . split ( oldState ) ,
298
+ } ,
299
+ newState : {
300
+ ...AvailablePorts . split ( newState ) ,
301
+ } ,
302
+ } ;
303
+
304
+ this . _availablePorts = newState ;
305
+ this . notificationService . notifyAttachedBoardsDidChange ( event ) ;
306
+ }
307
+ }
308
+
309
+ private fromRpc ( detectedPort : RpcDetectedPort ) : DetectedPort {
310
+ const rpcPort = detectedPort . getPort ( ) ;
311
+ const port = rpcPort && this . fromRpcPort ( rpcPort ) ;
312
+ const boards = detectedPort . getMatchingBoardsList ( ) . map (
313
+ ( board ) =>
314
+ ( {
315
+ fqbn : board . getFqbn ( ) ,
316
+ name : board . getName ( ) || Unknown ,
317
+ port,
318
+ } as Board )
319
+ ) ;
320
+ return {
321
+ boards,
322
+ port,
323
+ } ;
344
324
}
345
325
346
- getAvailablePorts ( state : AvailablePorts = this . availablePorts ) : Port [ ] {
347
- const availablePorts : Port [ ] = [ ] ;
348
- for ( const portID of Object . keys ( state ) ) {
349
- const [ port ] = state [ portID ] ;
350
- availablePorts . push ( port ) ;
326
+ private fromRpcPort ( rpcPort : RpcPort ) : Port {
327
+ const port = {
328
+ address : rpcPort . getAddress ( ) ,
329
+ addressLabel : rpcPort . getLabel ( ) ,
330
+ protocol : rpcPort . getProtocol ( ) ,
331
+ protocolLabel : rpcPort . getProtocolLabel ( ) ,
332
+ properties : Port . Properties . create ( rpcPort . getPropertiesMap ( ) . toObject ( ) ) ,
333
+ } ;
334
+ return port ;
335
+ }
336
+ }
337
+
338
+ enum EventType {
339
+ Add ,
340
+ Remove ,
341
+ Quit ,
342
+ }
343
+ namespace EventType {
344
+ export function parse ( type : string ) : EventType {
345
+ const normalizedType = type . toLowerCase ( ) ;
346
+ switch ( normalizedType ) {
347
+ case 'add' :
348
+ return EventType . Add ;
349
+ case 'remove' :
350
+ return EventType . Remove ;
351
+ case 'quit' :
352
+ return EventType . Quit ;
353
+ default :
354
+ throw new Error (
355
+ `Unexpected 'BoardListWatchResponse' event type: '${ type } .'`
356
+ ) ;
351
357
}
352
- return availablePorts ;
353
358
}
354
359
}
360
+
361
+ interface DetectedPort {
362
+ port : Port | undefined ;
363
+ boards : Board [ ] ;
364
+ }
0 commit comments