arduino-ide/arduino-ide-extension/src/node/core-client-provider.ts
Akos Kitta 0942ef6450 Switched from grpc to @grpc/grpc-js.
This change is required to be able to support ARM arch
in the future.

 - Fixed the download links for armv7l Linux.
 - Checked in the generated code. [grpc/grpc-node#1497]
 - Made the code generation a manual step.
 - Made the `grpc-tools` dependency optional.
 - From now on, the serial port regex is case-insensitive.

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2020-07-13 13:30:06 +02:00

167 lines
6.9 KiB
TypeScript

import * as grpc from '@grpc/grpc-js';
import { inject, injectable } from 'inversify';
import { ToolOutputServiceServer } from '../common/protocol';
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';
import { Instance } from './cli-protocol/commands/common_pb';
import { InitReq, InitResp, UpdateIndexReq, UpdateIndexResp, UpdateLibrariesIndexResp, UpdateLibrariesIndexReq } from './cli-protocol/commands/commands_pb';
import { Event, Emitter } from '@theia/core/lib/common/event';
@injectable()
export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Client> {
@inject(ToolOutputServiceServer)
protected readonly toolOutputService: ToolOutputServiceServer;
protected readonly onIndexUpdatedEmitter = new Emitter<void>();
get onIndexUpdated(): Event<void> {
return this.onIndexUpdatedEmitter.event;
}
close(client: CoreClientProvider.Client): void {
client.client.close();
}
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) {
this.updateIndexes(this._client);
}
} else {
return super.reconcileClient(port);
}
}
protected async createClient(port: string | number): Promise<CoreClientProvider.Client> {
// https://github.com/agreatfool/grpc_tools_node_protoc_ts/blob/master/doc/grpcjs_support.md#usage
// @ts-ignore
const ArduinoCoreClient = grpc.makeClientConstructor(commandsGrpcPb['cc.arduino.cli.commands.ArduinoCore'], 'ArduinoCoreService') as any;
const client = new ArduinoCoreClient(`localhost:${port}`, grpc.credentials.createInsecure(), this.channelOptions) as ArduinoCoreClient;
const initReq = new InitReq();
initReq.setLibraryManagerOnly(false);
const initResp = await new Promise<InitResp>((resolve, reject) => {
let resp: InitResp | undefined = undefined;
const stream = client.init(initReq);
stream.on('data', (data: InitResp) => resp = data);
stream.on('end', () => resolve(resp));
stream.on('error', err => reject(err));
});
const instance = initResp.getInstance();
if (!instance) {
throw new Error(`Could not retrieve instance from the initialize response.`);
}
await this.updateIndexes({ instance, client });
return { instance, client };
}
protected async updateIndexes({ client, instance }: CoreClientProvider.Client): Promise<void> {
// in a separate promise, try and update the index
let indexUpdateSucceeded = true;
for (let i = 0; i < 10; i++) {
try {
await this.updateIndex({ client, instance });
indexUpdateSucceeded = true;
break;
} catch (e) {
this.toolOutputService.publishNewOutput("daemon", `Error while updating index in attempt ${i}: ${e}`);
}
}
if (!indexUpdateSucceeded) {
this.toolOutputService.publishNewOutput("daemon", `Was unable to update the index. Please restart to try again.`);
}
let libIndexUpdateSucceeded = true;
for (let i = 0; i < 10; i++) {
try {
await this.updateLibraryIndex({ client, instance });
libIndexUpdateSucceeded = true;
break;
} catch (e) {
this.toolOutputService.publishNewOutput("daemon", `Error while updating library index in attempt ${i}: ${e}`);
}
}
if (!libIndexUpdateSucceeded) {
this.toolOutputService.publishNewOutput("daemon", `Was unable to update the library index. Please restart to try again.`);
}
if (indexUpdateSucceeded && libIndexUpdateSucceeded) {
this.onIndexUpdatedEmitter.fire();
}
}
protected async updateLibraryIndex({ client, instance }: CoreClientProvider.Client): Promise<void> {
const req = new UpdateLibrariesIndexReq();
req.setInstance(instance);
const resp = client.updateLibrariesIndex(req);
let file: string | undefined;
resp.on('data', (data: UpdateLibrariesIndexResp) => {
const progress = data.getDownloadProgress();
if (progress) {
if (!file && progress.getFile()) {
file = `${progress.getFile()}`;
}
if (progress.getCompleted()) {
if (file) {
if (/\s/.test(file)) {
this.toolOutputService.publishNewOutput("daemon", `${file} completed.\n`);
} else {
this.toolOutputService.publishNewOutput("daemon", `Download of '${file}' completed.\n'`);
}
} else {
this.toolOutputService.publishNewOutput("daemon", `The library index has been successfully updated.\n'`);
}
file = undefined;
}
}
});
await new Promise<void>((resolve, reject) => {
resp.on('error', reject);
resp.on('end', resolve);
});
}
protected async updateIndex({ client, instance }: CoreClientProvider.Client): Promise<void> {
const updateReq = new UpdateIndexReq();
updateReq.setInstance(instance);
const updateResp = client.updateIndex(updateReq);
let file: string | undefined;
updateResp.on('data', (o: UpdateIndexResp) => {
const progress = o.getDownloadProgress();
if (progress) {
if (!file && progress.getFile()) {
file = `${progress.getFile()}`;
}
if (progress.getCompleted()) {
if (file) {
if (/\s/.test(file)) {
this.toolOutputService.publishNewOutput("daemon", `${file} completed.\n`);
} else {
this.toolOutputService.publishNewOutput("daemon", `Download of '${file}' completed.\n'`);
}
} else {
this.toolOutputService.publishNewOutput("daemon", `The index has been successfully updated.\n'`);
}
file = undefined;
}
}
});
await new Promise<void>((resolve, reject) => {
updateResp.on('error', reject);
updateResp.on('end', resolve);
});
}
}
export namespace CoreClientProvider {
export interface Client {
readonly client: ArduinoCoreClient;
readonly instance: Instance;
}
}