mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-11-09 18:38:33 +00:00
ATL-786: Progress indication for install.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
This commit is contained in:
@@ -29,7 +29,7 @@ import { ExamplesServiceImpl } from './examples-service-impl';
|
||||
import { ExamplesService, ExamplesServicePath } from '../common/protocol/examples-service';
|
||||
import { ExecutableService, ExecutableServicePath } from '../common/protocol/executable-service';
|
||||
import { ExecutableServiceImpl } from './executable-service-impl';
|
||||
import { OutputServicePath, OutputService } from '../common/protocol/output-service';
|
||||
import { ResponseServicePath, ResponseService } from '../common/protocol/response-service';
|
||||
import { NotificationServiceServerImpl } from './notification-service-server';
|
||||
import { NotificationServiceServer, NotificationServiceClient, NotificationServicePath } from '../common/protocol';
|
||||
import { BackendApplication } from './theia/core/backend-application';
|
||||
@@ -127,7 +127,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
|
||||
// Output service per connection.
|
||||
bind(ConnectionContainerModule).toConstantValue(ConnectionContainerModule.create(({ bindFrontendService }) => {
|
||||
bindFrontendService(OutputServicePath, OutputService);
|
||||
bindFrontendService(ResponseServicePath, ResponseService);
|
||||
}));
|
||||
|
||||
// Notify all connected frontend instances
|
||||
|
||||
@@ -4,17 +4,18 @@ import { notEmpty } from '@theia/core/lib/common/objects';
|
||||
import {
|
||||
BoardsService,
|
||||
Installable,
|
||||
BoardsPackage, Board, Port, BoardDetails, Tool, ConfigOption, ConfigValue, Programmer, OutputService, NotificationServiceServer, AvailablePorts, BoardWithPackage
|
||||
BoardsPackage, Board, Port, BoardDetails, Tool, ConfigOption, ConfigValue, Programmer, ResponseService, NotificationServiceServer, AvailablePorts, BoardWithPackage
|
||||
} from '../common/protocol';
|
||||
import {
|
||||
PlatformInstallRequest, PlatformInstallResponse, PlatformListRequest, PlatformListResponse, PlatformSearchRequest,
|
||||
PlatformSearchResponse, PlatformUninstallRequest, PlatformUninstallResponse
|
||||
PlatformInstallRequest, PlatformListRequest, PlatformListResponse, PlatformSearchRequest,
|
||||
PlatformSearchResponse, PlatformUninstallRequest
|
||||
} from './cli-protocol/cc/arduino/cli/commands/v1/core_pb';
|
||||
import { Platform } from './cli-protocol/cc/arduino/cli/commands/v1/common_pb';
|
||||
import { BoardDiscovery } from './board-discovery';
|
||||
import { CoreClientAware } from './core-client-provider';
|
||||
import { BoardDetailsRequest, BoardDetailsResponse, BoardSearchRequest } from './cli-protocol/cc/arduino/cli/commands/v1/board_pb';
|
||||
import { ListProgrammersAvailableForUploadRequest, ListProgrammersAvailableForUploadResponse } from './cli-protocol/cc/arduino/cli/commands/v1/upload_pb';
|
||||
import { InstallWithProgress } from './grpc-installable';
|
||||
|
||||
@injectable()
|
||||
export class BoardsServiceImpl extends CoreClientAware implements BoardsService {
|
||||
@@ -26,8 +27,8 @@ export class BoardsServiceImpl extends CoreClientAware implements BoardsService
|
||||
@named('discovery')
|
||||
protected discoveryLogger: ILogger;
|
||||
|
||||
@inject(OutputService)
|
||||
protected readonly outputService: OutputService;
|
||||
@inject(ResponseService)
|
||||
protected readonly responseService: ResponseService;
|
||||
|
||||
@inject(NotificationServiceServer)
|
||||
protected readonly notificationService: NotificationServiceServer;
|
||||
@@ -254,7 +255,7 @@ export class BoardsServiceImpl extends CoreClientAware implements BoardsService
|
||||
return [...packages.values()];
|
||||
}
|
||||
|
||||
async install(options: { item: BoardsPackage, version?: Installable.Version }): Promise<void> {
|
||||
async install(options: { item: BoardsPackage, progressId?: string, version?: Installable.Version }): Promise<void> {
|
||||
const item = options.item;
|
||||
const version = !!options.version ? options.version : item.availableVersions[0];
|
||||
const coreClient = await this.coreClient();
|
||||
@@ -270,17 +271,12 @@ export class BoardsServiceImpl extends CoreClientAware implements BoardsService
|
||||
|
||||
console.info('>>> Starting boards package installation...', item);
|
||||
const resp = client.platformInstall(req);
|
||||
resp.on('data', (r: PlatformInstallResponse) => {
|
||||
const prog = r.getProgress();
|
||||
if (prog && prog.getFile()) {
|
||||
this.outputService.append({ chunk: `downloading ${prog.getFile()}\n` });
|
||||
}
|
||||
});
|
||||
resp.on('data', InstallWithProgress.createDataCallback({ progressId: options.progressId, responseService: this.responseService }));
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
resp.on('end', resolve);
|
||||
resp.on('error', error => {
|
||||
this.outputService.append({ chunk: `Failed to install platform: ${item.id}.\n` });
|
||||
this.outputService.append({ chunk: error.toString() });
|
||||
this.responseService.appendToOutput({ chunk: `Failed to install platform: ${item.id}.\n` });
|
||||
this.responseService.appendToOutput({ chunk: error.toString() });
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
@@ -291,8 +287,8 @@ export class BoardsServiceImpl extends CoreClientAware implements BoardsService
|
||||
console.info('<<< Boards package installation done.', item);
|
||||
}
|
||||
|
||||
async uninstall(options: { item: BoardsPackage }): Promise<void> {
|
||||
const item = options.item;
|
||||
async uninstall(options: { item: BoardsPackage, progressId?: string }): Promise<void> {
|
||||
const { item, progressId } = options;
|
||||
const coreClient = await this.coreClient();
|
||||
const { client, instance } = coreClient;
|
||||
|
||||
@@ -304,14 +300,8 @@ export class BoardsServiceImpl extends CoreClientAware implements BoardsService
|
||||
req.setPlatformPackage(platform);
|
||||
|
||||
console.info('>>> Starting boards package uninstallation...', item);
|
||||
let logged = false;
|
||||
const resp = client.platformUninstall(req);
|
||||
resp.on('data', (_: PlatformUninstallResponse) => {
|
||||
if (!logged) {
|
||||
this.outputService.append({ chunk: `uninstalling ${item.id}\n` });
|
||||
logged = true;
|
||||
}
|
||||
})
|
||||
resp.on('data', InstallWithProgress.createDataCallback({ progressId, responseService: this.responseService }));
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
resp.on('end', resolve);
|
||||
resp.on('error', reject);
|
||||
|
||||
@@ -57,9 +57,7 @@ export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Cl
|
||||
if (!instance) {
|
||||
throw new Error('Could not retrieve instance from the initialize response.');
|
||||
}
|
||||
|
||||
// No `await`. The index update event comes later. This way we do not block app startup with index update when invalid proxy is given.
|
||||
this.updateIndexes({ instance, client });
|
||||
await this.updateIndexes({ instance, client });
|
||||
|
||||
return { instance, client };
|
||||
}
|
||||
|
||||
@@ -2,22 +2,22 @@ import { FileUri } from '@theia/core/lib/node/file-uri';
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { relative } from 'path';
|
||||
import * as jspb from 'google-protobuf';
|
||||
import { BoolValue } from 'google-protobuf/google/protobuf/wrappers_pb';
|
||||
import { ClientReadableStream } from '@grpc/grpc-js';
|
||||
import { CompilerWarnings, CoreService } from '../common/protocol/core-service';
|
||||
import { CompileRequest, CompileResponse } from './cli-protocol/cc/arduino/cli/commands/v1/compile_pb';
|
||||
import { CoreClientAware } from './core-client-provider';
|
||||
import { BurnBootloaderRequest, BurnBootloaderResponse, UploadRequest, UploadResponse, UploadUsingProgrammerRequest, UploadUsingProgrammerResponse } from './cli-protocol/cc/arduino/cli/commands/v1/upload_pb';
|
||||
import { OutputService } from '../common/protocol/output-service';
|
||||
import { ResponseService } from '../common/protocol/response-service';
|
||||
import { NotificationServiceServer } from '../common/protocol';
|
||||
import { ClientReadableStream } from '@grpc/grpc-js';
|
||||
import { ArduinoCoreServiceClient } from './cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb';
|
||||
import { firstToUpperCase, firstToLowerCase } from '../common/utils';
|
||||
import { BoolValue } from 'google-protobuf/google/protobuf/wrappers_pb';
|
||||
|
||||
@injectable()
|
||||
export class CoreServiceImpl extends CoreClientAware implements CoreService {
|
||||
|
||||
@inject(OutputService)
|
||||
protected readonly outputService: OutputService;
|
||||
@inject(ResponseService)
|
||||
protected readonly responseService: ResponseService;
|
||||
|
||||
@inject(NotificationServiceServer)
|
||||
protected readonly notificationService: NotificationServiceServer;
|
||||
@@ -53,15 +53,15 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
|
||||
try {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
result.on('data', (cr: CompileResponse) => {
|
||||
this.outputService.append({ chunk: Buffer.from(cr.getOutStream_asU8()).toString() });
|
||||
this.outputService.append({ chunk: Buffer.from(cr.getErrStream_asU8()).toString() });
|
||||
this.responseService.appendToOutput({ chunk: Buffer.from(cr.getOutStream_asU8()).toString() });
|
||||
this.responseService.appendToOutput({ chunk: Buffer.from(cr.getErrStream_asU8()).toString() });
|
||||
});
|
||||
result.on('error', error => reject(error));
|
||||
result.on('end', () => resolve());
|
||||
});
|
||||
this.outputService.append({ chunk: '\n--------------------------\nCompilation complete.\n' });
|
||||
this.responseService.appendToOutput({ chunk: '\n--------------------------\nCompilation complete.\n' });
|
||||
} catch (e) {
|
||||
this.outputService.append({ chunk: `Compilation error: ${e}\n`, severity: 'error' });
|
||||
this.responseService.appendToOutput({ chunk: `Compilation error: ${e}\n`, severity: 'error' });
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@@ -107,15 +107,15 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
|
||||
try {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
result.on('data', (resp: UploadResponse) => {
|
||||
this.outputService.append({ chunk: Buffer.from(resp.getOutStream_asU8()).toString() });
|
||||
this.outputService.append({ chunk: Buffer.from(resp.getErrStream_asU8()).toString() });
|
||||
this.responseService.appendToOutput({ chunk: Buffer.from(resp.getOutStream_asU8()).toString() });
|
||||
this.responseService.appendToOutput({ chunk: Buffer.from(resp.getErrStream_asU8()).toString() });
|
||||
});
|
||||
result.on('error', error => reject(error));
|
||||
result.on('end', () => resolve());
|
||||
});
|
||||
this.outputService.append({ chunk: '\n--------------------------\n' + firstToLowerCase(task) + ' complete.\n' });
|
||||
this.responseService.appendToOutput({ chunk: '\n--------------------------\n' + firstToLowerCase(task) + ' complete.\n' });
|
||||
} catch (e) {
|
||||
this.outputService.append({ chunk: `${firstToUpperCase(task)} error: ${e}\n`, severity: 'error' });
|
||||
this.responseService.appendToOutput({ chunk: `${firstToUpperCase(task)} error: ${e}\n`, severity: 'error' });
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@@ -141,14 +141,14 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
|
||||
try {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
result.on('data', (resp: BurnBootloaderResponse) => {
|
||||
this.outputService.append({ chunk: Buffer.from(resp.getOutStream_asU8()).toString() });
|
||||
this.outputService.append({ chunk: Buffer.from(resp.getErrStream_asU8()).toString() });
|
||||
this.responseService.appendToOutput({ chunk: Buffer.from(resp.getOutStream_asU8()).toString() });
|
||||
this.responseService.appendToOutput({ chunk: Buffer.from(resp.getErrStream_asU8()).toString() });
|
||||
});
|
||||
result.on('error', error => reject(error));
|
||||
result.on('end', () => resolve());
|
||||
});
|
||||
} catch (e) {
|
||||
this.outputService.append({ chunk: `Error while burning the bootloader: ${e}\n`, severity: 'error' });
|
||||
this.responseService.appendToOutput({ chunk: `Error while burning the bootloader: ${e}\n`, severity: 'error' });
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ export abstract class GrpcClientProvider<C> {
|
||||
const updateClient = () => {
|
||||
const cliConfig = this.configService.cliConfiguration;
|
||||
this.reconcileClient(cliConfig ? cliConfig.daemon.port : undefined);
|
||||
}
|
||||
};
|
||||
this.configService.onConfigChange(updateClient);
|
||||
this.daemon.ready.then(updateClient);
|
||||
this.daemon.onDaemonStopped(() => {
|
||||
@@ -33,7 +33,7 @@ export abstract class GrpcClientProvider<C> {
|
||||
}
|
||||
this._client = undefined;
|
||||
this._port = undefined;
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async client(): Promise<C | Error | undefined> {
|
||||
|
||||
72
arduino-ide-extension/src/node/grpc-installable.ts
Normal file
72
arduino-ide-extension/src/node/grpc-installable.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { ProgressMessage, ResponseService } from '../common/protocol/response-service';
|
||||
import { DownloadProgress, TaskProgress } from './cli-protocol/cc/arduino/cli/commands/v1/common_pb';
|
||||
|
||||
export interface InstallResponse {
|
||||
getProgress?(): DownloadProgress | undefined;
|
||||
getTaskProgress(): TaskProgress | undefined;
|
||||
}
|
||||
|
||||
export namespace InstallWithProgress {
|
||||
|
||||
export interface Options {
|
||||
/**
|
||||
* _unknown_ progress if falsy.
|
||||
*/
|
||||
readonly progressId?: string;
|
||||
readonly responseService: ResponseService;
|
||||
}
|
||||
|
||||
export function createDataCallback({ responseService, progressId }: InstallWithProgress.Options): (response: InstallResponse) => void {
|
||||
let localFile = '';
|
||||
let localTotalSize = Number.NaN;
|
||||
return (response: InstallResponse) => {
|
||||
const download = response.getProgress ? response.getProgress() : undefined;
|
||||
const task = response.getTaskProgress();
|
||||
if (!download && !task) {
|
||||
throw new Error("Implementation error. Neither 'download' nor 'task' is available.");
|
||||
}
|
||||
if (task && download) {
|
||||
throw new Error("Implementation error. Both 'download' and 'task' are available.");
|
||||
}
|
||||
if (task) {
|
||||
const message = task.getName() || task.getMessage();
|
||||
if (message) {
|
||||
if (progressId) {
|
||||
responseService.reportProgress({ progressId, message, work: { done: Number.NaN, total: Number.NaN } });
|
||||
}
|
||||
responseService.appendToOutput({ chunk: `${message}\n` });
|
||||
}
|
||||
} else if (download) {
|
||||
if (download.getFile() && !localFile) {
|
||||
localFile = download.getFile();
|
||||
}
|
||||
if (download.getTotalSize() > 0 && Number.isNaN(localTotalSize)) {
|
||||
localTotalSize = download.getTotalSize();
|
||||
}
|
||||
|
||||
// This happens only once per file download.
|
||||
if (download.getTotalSize() && localFile) {
|
||||
responseService.appendToOutput({ chunk: `${localFile}\n` });
|
||||
}
|
||||
|
||||
if (progressId && localFile) {
|
||||
let work: ProgressMessage.Work | undefined = undefined;
|
||||
if (download.getDownloaded() > 0 && !Number.isNaN(localTotalSize)) {
|
||||
work = { total: localTotalSize, done: download.getDownloaded() };
|
||||
}
|
||||
responseService.reportProgress({ progressId, message: `Downloading ${localFile}`, work });
|
||||
}
|
||||
if (download.getCompleted()) {
|
||||
// Discard local state.
|
||||
if (progressId && !Number.isNaN(localTotalSize)) {
|
||||
responseService.reportProgress({ progressId, message: '', work: { done: Number.NaN, total: Number.NaN } });
|
||||
}
|
||||
localFile = '';
|
||||
localTotalSize = Number.NaN;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,14 +2,15 @@ import { injectable, inject } from 'inversify';
|
||||
import { LibraryDependency, LibraryLocation, LibraryPackage, LibraryService } from '../common/protocol/library-service';
|
||||
import { CoreClientAware } from './core-client-provider';
|
||||
import {
|
||||
InstalledLibrary, Library, LibraryInstallRequest, LibraryInstallResponse, LibraryListRequest, LibraryListResponse, LibraryLocation as GrpcLibraryLocation, LibraryRelease,
|
||||
LibraryResolveDependenciesRequest, LibraryUninstallRequest, LibraryUninstallResponse, ZipLibraryInstallRequest, ZipLibraryInstallResponse, LibrarySearchRequest,
|
||||
InstalledLibrary, Library, LibraryInstallRequest, LibraryListRequest, LibraryListResponse, LibraryLocation as GrpcLibraryLocation, LibraryRelease,
|
||||
LibraryResolveDependenciesRequest, LibraryUninstallRequest, ZipLibraryInstallRequest, LibrarySearchRequest,
|
||||
LibrarySearchResponse
|
||||
} from './cli-protocol/cc/arduino/cli/commands/v1/lib_pb';
|
||||
import { Installable } from '../common/protocol/installable';
|
||||
import { ILogger, notEmpty } from '@theia/core';
|
||||
import { FileUri } from '@theia/core/lib/node';
|
||||
import { OutputService, NotificationServiceServer } from '../common/protocol';
|
||||
import { ResponseService, NotificationServiceServer } from '../common/protocol';
|
||||
import { InstallWithProgress } from './grpc-installable';
|
||||
|
||||
@injectable()
|
||||
export class LibraryServiceImpl extends CoreClientAware implements LibraryService {
|
||||
@@ -17,8 +18,8 @@ export class LibraryServiceImpl extends CoreClientAware implements LibraryServic
|
||||
@inject(ILogger)
|
||||
protected logger: ILogger;
|
||||
|
||||
@inject(OutputService)
|
||||
protected readonly outputService: OutputService;
|
||||
@inject(ResponseService)
|
||||
protected readonly responseService: ResponseService;
|
||||
|
||||
@inject(NotificationServiceServer)
|
||||
protected readonly notificationServer: NotificationServiceServer;
|
||||
@@ -157,7 +158,7 @@ export class LibraryServiceImpl extends CoreClientAware implements LibraryServic
|
||||
return filterSelf ? dependencies.filter(({ name }) => name !== item.name) : dependencies;
|
||||
}
|
||||
|
||||
async install(options: { item: LibraryPackage, version?: Installable.Version, installDependencies?: boolean }): Promise<void> {
|
||||
async install(options: { item: LibraryPackage, progressId?: string, version?: Installable.Version, installDependencies?: boolean }): Promise<void> {
|
||||
const item = options.item;
|
||||
const version = !!options.version ? options.version : item.availableVersions[0];
|
||||
const coreClient = await this.coreClient();
|
||||
@@ -173,17 +174,12 @@ export class LibraryServiceImpl extends CoreClientAware implements LibraryServic
|
||||
|
||||
console.info('>>> Starting library package installation...', item);
|
||||
const resp = client.libraryInstall(req);
|
||||
resp.on('data', (r: LibraryInstallResponse) => {
|
||||
const prog = r.getProgress();
|
||||
if (prog) {
|
||||
this.outputService.append({ chunk: `downloading ${prog.getFile()}: ${prog.getCompleted()}%\n` });
|
||||
}
|
||||
});
|
||||
resp.on('data', InstallWithProgress.createDataCallback({ progressId: options.progressId, responseService: this.responseService }));
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
resp.on('end', resolve);
|
||||
resp.on('error', error => {
|
||||
this.outputService.append({ chunk: `Failed to install library: ${item.name}${version ? `:${version}` : ''}.\n` });
|
||||
this.outputService.append({ chunk: error.toString() });
|
||||
this.responseService.appendToOutput({ chunk: `Failed to install library: ${item.name}${version ? `:${version}` : ''}.\n` });
|
||||
this.responseService.appendToOutput({ chunk: error.toString() });
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
@@ -194,7 +190,7 @@ export class LibraryServiceImpl extends CoreClientAware implements LibraryServic
|
||||
console.info('<<< Library package installation done.', item);
|
||||
}
|
||||
|
||||
async installZip({ zipUri, overwrite }: { zipUri: string, overwrite?: boolean }): Promise<void> {
|
||||
async installZip({ zipUri, progressId, overwrite }: { zipUri: string, progressId?: string, overwrite?: boolean }): Promise<void> {
|
||||
const coreClient = await this.coreClient();
|
||||
const { client, instance } = coreClient;
|
||||
const req = new ZipLibraryInstallRequest();
|
||||
@@ -204,20 +200,15 @@ export class LibraryServiceImpl extends CoreClientAware implements LibraryServic
|
||||
req.setOverwrite(overwrite);
|
||||
}
|
||||
const resp = client.zipLibraryInstall(req);
|
||||
resp.on('data', (r: ZipLibraryInstallResponse) => {
|
||||
const task = r.getTaskProgress();
|
||||
if (task && task.getMessage()) {
|
||||
this.outputService.append({ chunk: task.getMessage() });
|
||||
}
|
||||
});
|
||||
resp.on('data', InstallWithProgress.createDataCallback({ progressId, responseService: this.responseService }));
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
resp.on('end', resolve);
|
||||
resp.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
async uninstall(options: { item: LibraryPackage }): Promise<void> {
|
||||
const item = options.item;
|
||||
async uninstall(options: { item: LibraryPackage, progressId?: string }): Promise<void> {
|
||||
const { item, progressId } = options;
|
||||
const coreClient = await this.coreClient();
|
||||
const { client, instance } = coreClient;
|
||||
|
||||
@@ -227,14 +218,8 @@ export class LibraryServiceImpl extends CoreClientAware implements LibraryServic
|
||||
req.setVersion(item.installedVersion!);
|
||||
|
||||
console.info('>>> Starting library package uninstallation...', item);
|
||||
let logged = false;
|
||||
const resp = client.libraryUninstall(req);
|
||||
resp.on('data', (_: LibraryUninstallResponse) => {
|
||||
if (!logged) {
|
||||
this.outputService.append({ chunk: `uninstalling ${item.name}:${item.installedVersion}%\n` });
|
||||
logged = true;
|
||||
}
|
||||
});
|
||||
resp.on('data', InstallWithProgress.createDataCallback({ progressId, responseService: this.responseService }));
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
resp.on('end', resolve);
|
||||
resp.on('error', reject);
|
||||
|
||||
Reference in New Issue
Block a user