ATL-1137: Show error when could not connect to CLI

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
This commit is contained in:
Akos Kitta 2021-03-24 16:03:21 +01:00 committed by Akos Kitta
parent a3f7b795a0
commit 26a1db3cf8
6 changed files with 39 additions and 41 deletions

3
.vscode/launch.json vendored
View File

@ -29,9 +29,10 @@
"NODE_PRESERVE_SYMLINKS": "1"
}
},
"program": "${workspaceRoot}/electron-app/src-gen/frontend/electron-main.js",
"cwd": "${workspaceFolder}/electron-app",
"protocol": "inspector",
"args": [
".",
"--log-level=debug",
"--hostname=localhost",
"--no-cluster",

View File

@ -6,6 +6,7 @@ import { MessageService } from '@theia/core/lib/common/message-service';
import { ApplicationServer } from '@theia/core/lib/common/application-protocol';
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
import { FocusTracker, Widget } from '@theia/core/lib/browser';
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
import { WorkspaceService as TheiaWorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
import { ConfigService } from '../../../common/protocol/config-service';
import { SketchesService, Sketch, SketchContainer } from '../../../common/protocol/sketches-service';
@ -29,10 +30,15 @@ export class WorkspaceService extends TheiaWorkspaceService {
@inject(ApplicationServer)
protected readonly applicationServer: ApplicationServer;
@inject(FrontendApplicationStateService)
protected readonly appStateService: FrontendApplicationStateService;
private application: FrontendApplication;
private workspaceUri?: Promise<string | undefined>;
private version?: string
private version?: string;
async onStart(application: FrontendApplication): Promise<void> {
this.application = application;
const info = await this.applicationServer.getApplicationInfo();
this.version = info?.version;
application.shell.onDidChangeCurrentWidget(this.onCurrentWidgetChange.bind(this));
@ -62,11 +68,12 @@ export class WorkspaceService extends TheiaWorkspaceService {
}
return (await this.sketchService.createNewSketch()).uri;
} catch (err) {
this.appStateService.reachedState('ready').then(() => this.application.shell.update());
this.logger.fatal(`Failed to determine the sketch directory: ${err}`)
this.messageService.error(
'There was an error creating the sketch directory. ' +
'See the log for more details. ' +
'The application will probably not work as expected.')
'The application will probably not work as expected.');
return super.getDefaultWorkspaceUri();
}
})();

View File

@ -2,7 +2,7 @@ import { injectable, inject, postConstruct, named } from 'inversify';
import { ClientDuplexStream } from '@grpc/grpc-js';
import { ILogger } from '@theia/core/lib/common/logger';
import { deepClone } from '@theia/core/lib/common/objects';
import { CoreClientProvider } from './core-client-provider';
import { CoreClientAware } from './core-client-provider';
import { BoardListWatchReq, BoardListWatchResp } from './cli-protocol/commands/board_pb';
import { Board, Port, NotificationServiceServer, AvailablePorts, AttachedBoardsChangeEvent } from '../common/protocol';
@ -12,15 +12,12 @@ import { Board, Port, NotificationServiceServer, AvailablePorts, AttachedBoardsC
* Unlike other services, this is not connection scoped.
*/
@injectable()
export class BoardDiscovery {
export class BoardDiscovery extends CoreClientAware {
@inject(ILogger)
@named('discovery')
protected discoveryLogger: ILogger;
@inject(CoreClientProvider)
protected readonly coreClientProvider: CoreClientProvider;
@inject(NotificationServiceServer)
protected readonly notificationService: NotificationServiceServer;
@ -133,23 +130,4 @@ export class BoardDiscovery {
return availablePorts;
}
private async coreClient(): Promise<CoreClientProvider.Client> {
const coreClient = await new Promise<CoreClientProvider.Client>(async resolve => {
const client = await this.coreClientProvider.client();
if (client) {
resolve(client);
return;
}
const toDispose = this.coreClientProvider.onClientReady(async () => {
const client = await this.coreClientProvider.client();
if (client) {
toDispose.dispose();
resolve(client);
return;
}
});
});
return coreClient;
}
}

View File

@ -1,6 +1,7 @@
import * as grpc from '@grpc/grpc-js';
import { inject, injectable } from 'inversify';
import { Event, Emitter } from '@theia/core/lib/common/event';
import { DisposableCollection } from '@theia/core/lib/common/disposable';
import { GrpcClientProvider } from './grpc-client-provider';
import { ArduinoCoreClient } from './cli-protocol/commands/commands_grpc_pb';
import * as commandsGrpcPb from './cli-protocol/commands/commands_grpc_pb';
@ -27,7 +28,7 @@ export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Cl
protected async reconcileClient(port: string | undefined): Promise<void> {
if (port && port === this._port) {
// No need to create a new gRPC client, but we have to update the indexes.
if (this._client) {
if (this._client && !(this._client instanceof Error)) {
await this.updateIndexes(this._client);
this.onClientReadyEmitter.fire();
}
@ -48,7 +49,7 @@ export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Cl
let resp: InitResp | undefined = undefined;
const stream = client.init(initReq);
stream.on('data', (data: InitResp) => resp = data);
stream.on('end', () => resolve(resp));
stream.on('end', () => resolve(resp!));
stream.on('error', err => reject(err));
});
@ -175,20 +176,27 @@ export abstract class CoreClientAware {
protected readonly coreClientProvider: CoreClientProvider;
protected async coreClient(): Promise<CoreClientProvider.Client> {
const coreClient = await new Promise<CoreClientProvider.Client>(async resolve => {
const coreClient = await new Promise<CoreClientProvider.Client>(async (resolve, reject) => {
const handle = (c: CoreClientProvider.Client | Error) => {
if (c instanceof Error) {
reject(c);
} else {
resolve(c);
}
}
const client = await this.coreClientProvider.client();
if (client) {
resolve(client);
handle(client);
return;
}
const toDispose = this.coreClientProvider.onClientReady(async () => {
const toDispose = new DisposableCollection();
toDispose.push(this.coreClientProvider.onClientReady(async () => {
const client = await this.coreClientProvider.client();
if (client) {
toDispose.dispose();
resolve(client);
return;
handle(client);
}
});
toDispose.dispose();
}));
});
return coreClient;
}

View File

@ -17,7 +17,7 @@ export abstract class GrpcClientProvider<C> {
protected readonly configService: ConfigServiceImpl;
protected _port: string | number | undefined;
protected _client: C | undefined;
protected _client: C | Error | undefined;
@postConstruct()
protected init(): void {
@ -28,7 +28,7 @@ export abstract class GrpcClientProvider<C> {
this.configService.onConfigChange(updateClient);
this.daemon.ready.then(updateClient);
this.daemon.onDaemonStopped(() => {
if (this._client) {
if (this._client && !(this._client instanceof Error)) {
this.close(this._client);
}
this._client = undefined;
@ -36,12 +36,12 @@ export abstract class GrpcClientProvider<C> {
})
}
async client(): Promise<C | undefined> {
async client(): Promise<C | Error | undefined> {
try {
await this.daemon.ready;
return this._client;
} catch (error) {
return undefined;
return error;
}
}
@ -50,7 +50,7 @@ export abstract class GrpcClientProvider<C> {
return; // Nothing to do.
}
this._port = port;
if (this._client) {
if (this._client && !(this._client instanceof Error)) {
this.close(this._client);
this._client = undefined;
}
@ -60,6 +60,7 @@ export abstract class GrpcClientProvider<C> {
this._client = client;
} catch (error) {
this.logger.error('Could not create client for gRPC.', error)
this._client = error;
}
}
}

View File

@ -72,6 +72,9 @@ export class MonitorServiceImpl implements MonitorService {
if (!client) {
return Status.NOT_CONNECTED;
}
if (client instanceof Error) {
return { message: client.message };
}
const duplex = client.streamingOpen();
this.connection = { duplex, config };