mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-06-05 03:36:35 +00:00
Update package index on 3rd party URLs change.
Closes #637 Closes #906 Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
parent
1073c3fc7d
commit
a36524e02a
@ -163,7 +163,7 @@ import { MonacoTextModelService } from './theia/monaco/monaco-text-model-service
|
||||
import { ResponseServiceImpl } from './response-service-impl';
|
||||
import {
|
||||
ResponseService,
|
||||
ResponseServiceArduino,
|
||||
ResponseServiceClient,
|
||||
ResponseServicePath,
|
||||
} from '../common/protocol/response-service';
|
||||
import { NotificationCenter } from './notification-center';
|
||||
@ -302,6 +302,8 @@ import { CompilerErrors } from './contributions/compiler-errors';
|
||||
import { WidgetManager } from './theia/core/widget-manager';
|
||||
import { WidgetManager as TheiaWidgetManager } from '@theia/core/lib/browser/widget-manager';
|
||||
import { StartupTask } from './widgets/sketchbook/startup-task';
|
||||
import { IndexesUpdateProgress } from './contributions/indexes-update-progress';
|
||||
import { Daemon } from './contributions/daemon';
|
||||
|
||||
MonacoThemingService.register({
|
||||
id: 'arduino-theme',
|
||||
@ -695,6 +697,8 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
Contribution.configure(bind, Format);
|
||||
Contribution.configure(bind, CompilerErrors);
|
||||
Contribution.configure(bind, StartupTask);
|
||||
Contribution.configure(bind, IndexesUpdateProgress);
|
||||
Contribution.configure(bind, Daemon);
|
||||
|
||||
// Disabled the quick-pick customization from Theia when multiple formatters are available.
|
||||
// Use the default VS Code behavior, and pick the first one. In the IDE2, clang-format has `exclusive` selectors.
|
||||
@ -716,7 +720,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
});
|
||||
|
||||
bind(ResponseService).toService(ResponseServiceImpl);
|
||||
bind(ResponseServiceArduino).toService(ResponseServiceImpl);
|
||||
bind(ResponseServiceClient).toService(ResponseServiceImpl);
|
||||
|
||||
bind(NotificationCenter).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(NotificationCenter);
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
Port,
|
||||
} from '../../common/protocol/boards-service';
|
||||
import { BoardsServiceProvider } from './boards-service-provider';
|
||||
import { Installable, ResponseServiceArduino } from '../../common/protocol';
|
||||
import { Installable, ResponseServiceClient } from '../../common/protocol';
|
||||
import { BoardsListWidgetFrontendContribution } from './boards-widget-frontend-contribution';
|
||||
import { nls } from '@theia/core/lib/common';
|
||||
import { NotificationCenter } from '../notification-center';
|
||||
@ -45,8 +45,8 @@ export class BoardsAutoInstaller implements FrontendApplicationContribution {
|
||||
@inject(BoardsServiceProvider)
|
||||
protected readonly boardsServiceClient: BoardsServiceProvider;
|
||||
|
||||
@inject(ResponseServiceArduino)
|
||||
protected readonly responseService: ResponseServiceArduino;
|
||||
@inject(ResponseServiceClient)
|
||||
protected readonly responseService: ResponseServiceClient;
|
||||
|
||||
@inject(BoardsListWidgetFrontendContribution)
|
||||
protected readonly boardsManagerFrontendContribution: BoardsListWidgetFrontendContribution;
|
||||
@ -86,7 +86,7 @@ export class BoardsAutoInstaller implements FrontendApplicationContribution {
|
||||
// installed, though this is not strictly necessary. It's more of a
|
||||
// cleanup, to ensure the related variables are representative of
|
||||
// current state.
|
||||
this.notificationCenter.onPlatformInstalled((installed) => {
|
||||
this.notificationCenter.onPlatformDidInstall((installed) => {
|
||||
if (this.lastRefusedPackageId === installed.item.id) {
|
||||
this.clearLastRefusedPromptInfo();
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ export class BoardsConfig extends React.Component<
|
||||
);
|
||||
}
|
||||
}),
|
||||
this.props.notificationCenter.onAttachedBoardsChanged((event) =>
|
||||
this.props.notificationCenter.onAttachedBoardsDidChange((event) =>
|
||||
this.updatePorts(
|
||||
event.newState.ports,
|
||||
AttachedBoardsChangeEvent.diff(event).detached.ports
|
||||
@ -126,19 +126,19 @@ export class BoardsConfig extends React.Component<
|
||||
);
|
||||
}
|
||||
),
|
||||
this.props.notificationCenter.onPlatformInstalled(() =>
|
||||
this.props.notificationCenter.onPlatformDidInstall(() =>
|
||||
this.updateBoards(this.state.query)
|
||||
),
|
||||
this.props.notificationCenter.onPlatformUninstalled(() =>
|
||||
this.props.notificationCenter.onPlatformDidUninstall(() =>
|
||||
this.updateBoards(this.state.query)
|
||||
),
|
||||
this.props.notificationCenter.onIndexUpdated(() =>
|
||||
this.props.notificationCenter.onIndexDidUpdate(() =>
|
||||
this.updateBoards(this.state.query)
|
||||
),
|
||||
this.props.notificationCenter.onDaemonStarted(() =>
|
||||
this.props.notificationCenter.onDaemonDidStart(() =>
|
||||
this.updateBoards(this.state.query)
|
||||
),
|
||||
this.props.notificationCenter.onDaemonStopped(() =>
|
||||
this.props.notificationCenter.onDaemonDidStop(() =>
|
||||
this.setState({ searchResults: [] })
|
||||
),
|
||||
this.props.onFilteredTextDidChangeEvent((query) =>
|
||||
|
@ -33,7 +33,7 @@ export class BoardsDataStore implements FrontendApplicationContribution {
|
||||
protected readonly onChangedEmitter = new Emitter<void>();
|
||||
|
||||
onStart(): void {
|
||||
this.notificationCenter.onPlatformInstalled(async ({ item }) => {
|
||||
this.notificationCenter.onPlatformDidInstall(async ({ item }) => {
|
||||
let shouldFireChanged = false;
|
||||
for (const fqbn of item.boards
|
||||
.map(({ fqbn }) => fqbn)
|
||||
|
@ -33,10 +33,10 @@ export class BoardsListWidget extends ListWidget<BoardsPackage> {
|
||||
protected override init(): void {
|
||||
super.init();
|
||||
this.toDispose.pushAll([
|
||||
this.notificationCenter.onPlatformInstalled(() =>
|
||||
this.notificationCenter.onPlatformDidInstall(() =>
|
||||
this.refresh(undefined)
|
||||
),
|
||||
this.notificationCenter.onPlatformUninstalled(() =>
|
||||
this.notificationCenter.onPlatformDidUninstall(() =>
|
||||
this.refresh(undefined)
|
||||
),
|
||||
]);
|
||||
|
@ -77,13 +77,13 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
private readonly _reconciled = new Deferred<void>();
|
||||
|
||||
onStart(): void {
|
||||
this.notificationCenter.onAttachedBoardsChanged(
|
||||
this.notificationCenter.onAttachedBoardsDidChange(
|
||||
this.notifyAttachedBoardsChanged.bind(this)
|
||||
);
|
||||
this.notificationCenter.onPlatformInstalled(
|
||||
this.notificationCenter.onPlatformDidInstall(
|
||||
this.notifyPlatformInstalled.bind(this)
|
||||
);
|
||||
this.notificationCenter.onPlatformUninstalled(
|
||||
this.notificationCenter.onPlatformDidUninstall(
|
||||
this.notifyPlatformUninstalled.bind(this)
|
||||
);
|
||||
|
||||
|
@ -7,7 +7,7 @@ import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import {
|
||||
Installable,
|
||||
LibraryService,
|
||||
ResponseServiceArduino,
|
||||
ResponseServiceClient,
|
||||
} from '../../common/protocol';
|
||||
import {
|
||||
SketchContribution,
|
||||
@ -22,8 +22,8 @@ export class AddZipLibrary extends SketchContribution {
|
||||
@inject(EnvVariablesServer)
|
||||
protected readonly envVariableServer: EnvVariablesServer;
|
||||
|
||||
@inject(ResponseServiceArduino)
|
||||
protected readonly responseService: ResponseServiceArduino;
|
||||
@inject(ResponseServiceClient)
|
||||
protected readonly responseService: ResponseServiceClient;
|
||||
|
||||
@inject(LibraryService)
|
||||
protected readonly libraryService: LibraryService;
|
||||
|
@ -101,8 +101,8 @@ PID: ${PID}`;
|
||||
}
|
||||
|
||||
override onStart(): void {
|
||||
this.notificationCenter.onPlatformInstalled(() => this.updateMenus());
|
||||
this.notificationCenter.onPlatformUninstalled(() => this.updateMenus());
|
||||
this.notificationCenter.onPlatformDidInstall(() => this.updateMenus());
|
||||
this.notificationCenter.onPlatformDidUninstall(() => this.updateMenus());
|
||||
this.boardsServiceProvider.onBoardsConfigChanged(() => this.updateMenus());
|
||||
this.boardsServiceProvider.onAvailableBoardsChanged(() =>
|
||||
this.updateMenus()
|
||||
|
41
arduino-ide-extension/src/browser/contributions/daemon.ts
Normal file
41
arduino-ide-extension/src/browser/contributions/daemon.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { nls } from '@theia/core';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { ArduinoDaemon } from '../../common/protocol';
|
||||
import { Contribution, Command, CommandRegistry } from './contribution';
|
||||
|
||||
@injectable()
|
||||
export class Daemon extends Contribution {
|
||||
@inject(ArduinoDaemon)
|
||||
private readonly daemon: ArduinoDaemon;
|
||||
|
||||
override registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(Daemon.Commands.START_DAEMON, {
|
||||
execute: () => this.daemon.start(),
|
||||
});
|
||||
registry.registerCommand(Daemon.Commands.STOP_DAEMON, {
|
||||
execute: () => this.daemon.stop(),
|
||||
});
|
||||
registry.registerCommand(Daemon.Commands.RESTART_DAEMON, {
|
||||
execute: () => this.daemon.restart(),
|
||||
});
|
||||
}
|
||||
}
|
||||
export namespace Daemon {
|
||||
export namespace Commands {
|
||||
export const START_DAEMON: Command = {
|
||||
id: 'arduino-start-daemon',
|
||||
label: nls.localize('arduino/daemon/start', 'Start Daemon'),
|
||||
category: 'Arduino',
|
||||
};
|
||||
export const STOP_DAEMON: Command = {
|
||||
id: 'arduino-stop-daemon',
|
||||
label: nls.localize('arduino/daemon/stop', 'Stop Daemon'),
|
||||
category: 'Arduino',
|
||||
};
|
||||
export const RESTART_DAEMON: Command = {
|
||||
id: 'arduino-restart-daemon',
|
||||
label: nls.localize('arduino/daemon/restart', 'Restart Daemon'),
|
||||
category: 'Arduino',
|
||||
};
|
||||
}
|
||||
}
|
@ -83,8 +83,8 @@ export class Debug extends SketchContribution {
|
||||
this.boardsServiceProvider.onBoardsConfigChanged(({ selectedBoard }) =>
|
||||
this.refreshState(selectedBoard)
|
||||
);
|
||||
this.notificationCenter.onPlatformInstalled(() => this.refreshState());
|
||||
this.notificationCenter.onPlatformUninstalled(() => this.refreshState());
|
||||
this.notificationCenter.onPlatformDidInstall(() => this.refreshState());
|
||||
this.notificationCenter.onPlatformDidUninstall(() => this.refreshState());
|
||||
}
|
||||
|
||||
override onReady(): MaybePromise<void> {
|
||||
|
@ -202,8 +202,8 @@ export class LibraryExamples extends Examples {
|
||||
protected readonly queue = new PQueue({ autoStart: true, concurrency: 1 });
|
||||
|
||||
override onStart(): void {
|
||||
this.notificationCenter.onLibraryInstalled(() => this.register());
|
||||
this.notificationCenter.onLibraryUninstalled(() => this.register());
|
||||
this.notificationCenter.onLibraryDidInstall(() => this.register());
|
||||
this.notificationCenter.onLibraryDidUninstall(() => this.register());
|
||||
}
|
||||
|
||||
override async onReady(): Promise<void> {
|
||||
|
@ -49,8 +49,8 @@ export class IncludeLibrary extends SketchContribution {
|
||||
this.boardsServiceClient.onBoardsConfigChanged(() =>
|
||||
this.updateMenuActions()
|
||||
);
|
||||
this.notificationCenter.onLibraryInstalled(() => this.updateMenuActions());
|
||||
this.notificationCenter.onLibraryUninstalled(() =>
|
||||
this.notificationCenter.onLibraryDidInstall(() => this.updateMenuActions());
|
||||
this.notificationCenter.onLibraryDidUninstall(() =>
|
||||
this.updateMenuActions()
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,71 @@
|
||||
import { Progress } from '@theia/core/lib/common/message-service-protocol';
|
||||
import { ProgressService } from '@theia/core/lib/common/progress-service';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { ProgressMessage } from '../../common/protocol';
|
||||
import { NotificationCenter } from '../notification-center';
|
||||
import { Contribution } from './contribution';
|
||||
|
||||
@injectable()
|
||||
export class IndexesUpdateProgress extends Contribution {
|
||||
@inject(NotificationCenter)
|
||||
private readonly notificationCenter: NotificationCenter;
|
||||
@inject(ProgressService)
|
||||
private readonly progressService: ProgressService;
|
||||
private currentProgress:
|
||||
| (Progress & Readonly<{ progressId: string }>)
|
||||
| undefined;
|
||||
|
||||
override onStart(): void {
|
||||
this.notificationCenter.onIndexWillUpdate((progressId) =>
|
||||
this.getOrCreateProgress(progressId)
|
||||
);
|
||||
this.notificationCenter.onIndexUpdateDidProgress((progress) => {
|
||||
this.getOrCreateProgress(progress).then((delegate) =>
|
||||
delegate.report(progress)
|
||||
);
|
||||
});
|
||||
this.notificationCenter.onIndexDidUpdate((progressId) => {
|
||||
this.cancelProgress(progressId);
|
||||
});
|
||||
this.notificationCenter.onIndexUpdateDidFail(({ progressId, message }) => {
|
||||
this.cancelProgress(progressId);
|
||||
this.messageService.error(message);
|
||||
});
|
||||
}
|
||||
|
||||
private async getOrCreateProgress(
|
||||
progressOrId: ProgressMessage | string
|
||||
): Promise<Progress & { progressId: string }> {
|
||||
const progressId = ProgressMessage.is(progressOrId)
|
||||
? progressOrId.progressId
|
||||
: progressOrId;
|
||||
if (this.currentProgress?.progressId === progressId) {
|
||||
return this.currentProgress;
|
||||
}
|
||||
if (this.currentProgress) {
|
||||
this.currentProgress.cancel();
|
||||
}
|
||||
this.currentProgress = undefined;
|
||||
const progress = await this.progressService.showProgress({
|
||||
text: '',
|
||||
options: { location: 'notification' },
|
||||
});
|
||||
if (ProgressMessage.is(progressOrId)) {
|
||||
progress.report(progressOrId);
|
||||
}
|
||||
this.currentProgress = { ...progress, progressId };
|
||||
return this.currentProgress;
|
||||
}
|
||||
|
||||
private cancelProgress(progressId: string) {
|
||||
if (this.currentProgress) {
|
||||
if (this.currentProgress.progressId !== progressId) {
|
||||
console.warn(
|
||||
`Mismatching progress IDs. Expected ${progressId}, got ${this.currentProgress.progressId}. Canceling anyway.`
|
||||
);
|
||||
}
|
||||
this.currentProgress.cancel();
|
||||
this.currentProgress = undefined;
|
||||
}
|
||||
}
|
||||
}
|
@ -36,7 +36,7 @@ export class OpenRecentSketch extends SketchContribution {
|
||||
protected toDisposeBeforeRegister = new Map<string, DisposableCollection>();
|
||||
|
||||
override onStart(): void {
|
||||
this.notificationCenter.onRecentSketchesChanged(({ sketches }) =>
|
||||
this.notificationCenter.onRecentSketchesDidChange(({ sketches }) =>
|
||||
this.refreshMenu(sketches)
|
||||
);
|
||||
}
|
||||
|
@ -41,8 +41,8 @@ export class LibraryListWidget extends ListWidget<LibraryPackage> {
|
||||
protected override init(): void {
|
||||
super.init();
|
||||
this.toDispose.pushAll([
|
||||
this.notificationCenter.onLibraryInstalled(() => this.refresh(undefined)),
|
||||
this.notificationCenter.onLibraryUninstalled(() =>
|
||||
this.notificationCenter.onLibraryDidInstall(() => this.refresh(undefined)),
|
||||
this.notificationCenter.onLibraryDidUninstall(() =>
|
||||
this.refresh(undefined)
|
||||
),
|
||||
]);
|
||||
|
@ -17,6 +17,7 @@ import {
|
||||
LibraryPackage,
|
||||
Config,
|
||||
Sketch,
|
||||
ProgressMessage,
|
||||
} from '../common/protocol';
|
||||
import {
|
||||
FrontendApplicationStateService,
|
||||
@ -33,25 +34,32 @@ export class NotificationCenter
|
||||
@inject(FrontendApplicationStateService)
|
||||
private readonly appStateService: FrontendApplicationStateService;
|
||||
|
||||
protected readonly indexUpdatedEmitter = new Emitter<void>();
|
||||
protected readonly daemonStartedEmitter = new Emitter<string>();
|
||||
protected readonly daemonStoppedEmitter = new Emitter<void>();
|
||||
protected readonly configChangedEmitter = new Emitter<{
|
||||
protected readonly indexDidUpdateEmitter = new Emitter<string>();
|
||||
protected readonly indexWillUpdateEmitter = new Emitter<string>();
|
||||
protected readonly indexUpdateDidProgressEmitter =
|
||||
new Emitter<ProgressMessage>();
|
||||
protected readonly indexUpdateDidFailEmitter = new Emitter<{
|
||||
progressId: string;
|
||||
message: string;
|
||||
}>();
|
||||
protected readonly daemonDidStartEmitter = new Emitter<string>();
|
||||
protected readonly daemonDidStopEmitter = new Emitter<void>();
|
||||
protected readonly configDidChangeEmitter = new Emitter<{
|
||||
config: Config | undefined;
|
||||
}>();
|
||||
protected readonly platformInstalledEmitter = new Emitter<{
|
||||
protected readonly platformDidInstallEmitter = new Emitter<{
|
||||
item: BoardsPackage;
|
||||
}>();
|
||||
protected readonly platformUninstalledEmitter = new Emitter<{
|
||||
protected readonly platformDidUninstallEmitter = new Emitter<{
|
||||
item: BoardsPackage;
|
||||
}>();
|
||||
protected readonly libraryInstalledEmitter = new Emitter<{
|
||||
protected readonly libraryDidInstallEmitter = new Emitter<{
|
||||
item: LibraryPackage;
|
||||
}>();
|
||||
protected readonly libraryUninstalledEmitter = new Emitter<{
|
||||
protected readonly libraryDidUninstallEmitter = new Emitter<{
|
||||
item: LibraryPackage;
|
||||
}>();
|
||||
protected readonly attachedBoardsChangedEmitter =
|
||||
protected readonly attachedBoardsDidChangeEmitter =
|
||||
new Emitter<AttachedBoardsChangeEvent>();
|
||||
protected readonly recentSketchesChangedEmitter = new Emitter<{
|
||||
sketches: Sketch[];
|
||||
@ -60,27 +68,34 @@ export class NotificationCenter
|
||||
new Emitter<FrontendApplicationState>();
|
||||
|
||||
protected readonly toDispose = new DisposableCollection(
|
||||
this.indexUpdatedEmitter,
|
||||
this.daemonStartedEmitter,
|
||||
this.daemonStoppedEmitter,
|
||||
this.configChangedEmitter,
|
||||
this.platformInstalledEmitter,
|
||||
this.platformUninstalledEmitter,
|
||||
this.libraryInstalledEmitter,
|
||||
this.libraryUninstalledEmitter,
|
||||
this.attachedBoardsChangedEmitter
|
||||
this.indexWillUpdateEmitter,
|
||||
this.indexUpdateDidProgressEmitter,
|
||||
this.indexDidUpdateEmitter,
|
||||
this.indexUpdateDidFailEmitter,
|
||||
this.daemonDidStartEmitter,
|
||||
this.daemonDidStopEmitter,
|
||||
this.configDidChangeEmitter,
|
||||
this.platformDidInstallEmitter,
|
||||
this.platformDidUninstallEmitter,
|
||||
this.libraryDidInstallEmitter,
|
||||
this.libraryDidUninstallEmitter,
|
||||
this.attachedBoardsDidChangeEmitter
|
||||
);
|
||||
|
||||
readonly onIndexUpdated = this.indexUpdatedEmitter.event;
|
||||
readonly onDaemonStarted = this.daemonStartedEmitter.event;
|
||||
readonly onDaemonStopped = this.daemonStoppedEmitter.event;
|
||||
readonly onConfigChanged = this.configChangedEmitter.event;
|
||||
readonly onPlatformInstalled = this.platformInstalledEmitter.event;
|
||||
readonly onPlatformUninstalled = this.platformUninstalledEmitter.event;
|
||||
readonly onLibraryInstalled = this.libraryInstalledEmitter.event;
|
||||
readonly onLibraryUninstalled = this.libraryUninstalledEmitter.event;
|
||||
readonly onAttachedBoardsChanged = this.attachedBoardsChangedEmitter.event;
|
||||
readonly onRecentSketchesChanged = this.recentSketchesChangedEmitter.event;
|
||||
readonly onIndexDidUpdate = this.indexDidUpdateEmitter.event;
|
||||
readonly onIndexWillUpdate = this.indexDidUpdateEmitter.event;
|
||||
readonly onIndexUpdateDidProgress = this.indexUpdateDidProgressEmitter.event;
|
||||
readonly onIndexUpdateDidFail = this.indexUpdateDidFailEmitter.event;
|
||||
readonly onDaemonDidStart = this.daemonDidStartEmitter.event;
|
||||
readonly onDaemonDidStop = this.daemonDidStopEmitter.event;
|
||||
readonly onConfigDidChange = this.configDidChangeEmitter.event;
|
||||
readonly onPlatformDidInstall = this.platformDidInstallEmitter.event;
|
||||
readonly onPlatformDidUninstall = this.platformDidUninstallEmitter.event;
|
||||
readonly onLibraryDidInstall = this.libraryDidInstallEmitter.event;
|
||||
readonly onLibraryDidUninstall = this.libraryDidUninstallEmitter.event;
|
||||
readonly onAttachedBoardsDidChange =
|
||||
this.attachedBoardsDidChangeEmitter.event;
|
||||
readonly onRecentSketchesDidChange = this.recentSketchesChangedEmitter.event;
|
||||
readonly onAppStateDidChange = this.onAppStateDidChangeEmitter.event;
|
||||
|
||||
@postConstruct()
|
||||
@ -97,43 +112,61 @@ export class NotificationCenter
|
||||
this.toDispose.dispose();
|
||||
}
|
||||
|
||||
notifyIndexUpdated(): void {
|
||||
this.indexUpdatedEmitter.fire();
|
||||
notifyIndexWillUpdate(progressId: string): void {
|
||||
this.indexWillUpdateEmitter.fire(progressId);
|
||||
}
|
||||
|
||||
notifyDaemonStarted(port: string): void {
|
||||
this.daemonStartedEmitter.fire(port);
|
||||
notifyIndexUpdateDidProgress(progressMessage: ProgressMessage): void {
|
||||
this.indexUpdateDidProgressEmitter.fire(progressMessage);
|
||||
}
|
||||
|
||||
notifyDaemonStopped(): void {
|
||||
this.daemonStoppedEmitter.fire();
|
||||
notifyIndexDidUpdate(progressId: string): void {
|
||||
this.indexDidUpdateEmitter.fire(progressId);
|
||||
}
|
||||
|
||||
notifyConfigChanged(event: { config: Config | undefined }): void {
|
||||
this.configChangedEmitter.fire(event);
|
||||
notifyIndexUpdateDidFail({
|
||||
progressId,
|
||||
message,
|
||||
}: {
|
||||
progressId: string;
|
||||
message: string;
|
||||
}): void {
|
||||
this.indexUpdateDidFailEmitter.fire({ progressId, message });
|
||||
}
|
||||
|
||||
notifyPlatformInstalled(event: { item: BoardsPackage }): void {
|
||||
this.platformInstalledEmitter.fire(event);
|
||||
notifyDaemonDidStart(port: string): void {
|
||||
this.daemonDidStartEmitter.fire(port);
|
||||
}
|
||||
|
||||
notifyPlatformUninstalled(event: { item: BoardsPackage }): void {
|
||||
this.platformUninstalledEmitter.fire(event);
|
||||
notifyDaemonDidStop(): void {
|
||||
this.daemonDidStopEmitter.fire();
|
||||
}
|
||||
|
||||
notifyLibraryInstalled(event: { item: LibraryPackage }): void {
|
||||
this.libraryInstalledEmitter.fire(event);
|
||||
notifyConfigDidChange(event: { config: Config | undefined }): void {
|
||||
this.configDidChangeEmitter.fire(event);
|
||||
}
|
||||
|
||||
notifyLibraryUninstalled(event: { item: LibraryPackage }): void {
|
||||
this.libraryUninstalledEmitter.fire(event);
|
||||
notifyPlatformDidInstall(event: { item: BoardsPackage }): void {
|
||||
this.platformDidInstallEmitter.fire(event);
|
||||
}
|
||||
|
||||
notifyAttachedBoardsChanged(event: AttachedBoardsChangeEvent): void {
|
||||
this.attachedBoardsChangedEmitter.fire(event);
|
||||
notifyPlatformDidUninstall(event: { item: BoardsPackage }): void {
|
||||
this.platformDidUninstallEmitter.fire(event);
|
||||
}
|
||||
|
||||
notifyRecentSketchesChanged(event: { sketches: Sketch[] }): void {
|
||||
notifyLibraryDidInstall(event: { item: LibraryPackage }): void {
|
||||
this.libraryDidInstallEmitter.fire(event);
|
||||
}
|
||||
|
||||
notifyLibraryDidUninstall(event: { item: LibraryPackage }): void {
|
||||
this.libraryDidUninstallEmitter.fire(event);
|
||||
}
|
||||
|
||||
notifyAttachedBoardsDidChange(event: AttachedBoardsChangeEvent): void {
|
||||
this.attachedBoardsDidChangeEmitter.fire(event);
|
||||
}
|
||||
|
||||
notifyRecentSketchesDidChange(event: { sketches: Sketch[] }): void {
|
||||
this.recentSketchesChangedEmitter.fire(event);
|
||||
}
|
||||
}
|
||||
|
@ -7,11 +7,11 @@ import {
|
||||
import {
|
||||
OutputMessage,
|
||||
ProgressMessage,
|
||||
ResponseServiceArduino,
|
||||
ResponseServiceClient,
|
||||
} from '../common/protocol/response-service';
|
||||
|
||||
@injectable()
|
||||
export class ResponseServiceImpl implements ResponseServiceArduino {
|
||||
export class ResponseServiceImpl implements ResponseServiceClient {
|
||||
@inject(OutputChannelManager)
|
||||
private readonly outputChannelManager: OutputChannelManager;
|
||||
|
||||
@ -19,7 +19,7 @@ export class ResponseServiceImpl implements ResponseServiceArduino {
|
||||
|
||||
readonly onProgressDidChange = this.progressDidChangeEmitter.event;
|
||||
|
||||
clearArduinoChannel(): void {
|
||||
clearOutput(): void {
|
||||
this.outputChannelManager.getChannel('Arduino').clear();
|
||||
}
|
||||
|
||||
|
@ -30,10 +30,10 @@ export class FrontendConnectionStatusService extends TheiaFrontendConnectionStat
|
||||
try {
|
||||
this.connectedPort = await this.daemon.tryGetPort();
|
||||
} catch {}
|
||||
this.notificationCenter.onDaemonStarted(
|
||||
this.notificationCenter.onDaemonDidStart(
|
||||
(port) => (this.connectedPort = port)
|
||||
);
|
||||
this.notificationCenter.onDaemonStopped(
|
||||
this.notificationCenter.onDaemonDidStop(
|
||||
() => (this.connectedPort = undefined)
|
||||
);
|
||||
this.wsConnectionProvider.onIncomingMessageActivity(() => {
|
||||
@ -58,10 +58,10 @@ export class ApplicationConnectionStatusContribution extends TheiaApplicationCon
|
||||
try {
|
||||
this.connectedPort = await this.daemon.tryGetPort();
|
||||
} catch {}
|
||||
this.notificationCenter.onDaemonStarted(
|
||||
this.notificationCenter.onDaemonDidStart(
|
||||
(port) => (this.connectedPort = port)
|
||||
);
|
||||
this.notificationCenter.onDaemonStopped(
|
||||
this.notificationCenter.onDaemonDidStop(
|
||||
() => (this.connectedPort = undefined)
|
||||
);
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import { SearchBar } from './search-bar';
|
||||
import { ListWidget } from './list-widget';
|
||||
import { ComponentList } from './component-list';
|
||||
import { ListItemRenderer } from './list-item-renderer';
|
||||
import { ResponseServiceArduino } from '../../../common/protocol';
|
||||
import { ResponseServiceClient } from '../../../common/protocol';
|
||||
import { nls } from '@theia/core/lib/common';
|
||||
|
||||
export class FilterableListContainer<
|
||||
@ -162,7 +162,7 @@ export namespace FilterableListContainer {
|
||||
readonly resolveFocus: (element: HTMLElement | undefined) => void;
|
||||
readonly filterTextChangeEvent: Event<string | undefined>;
|
||||
readonly messageService: MessageService;
|
||||
readonly responseService: ResponseServiceArduino;
|
||||
readonly responseService: ResponseServiceClient;
|
||||
readonly install: ({
|
||||
item,
|
||||
progressId,
|
||||
|
@ -1,5 +1,9 @@
|
||||
import * as React from '@theia/core/shared/react';
|
||||
import { injectable, postConstruct, inject } from '@theia/core/shared/inversify';
|
||||
import {
|
||||
injectable,
|
||||
postConstruct,
|
||||
inject,
|
||||
} from '@theia/core/shared/inversify';
|
||||
import { Widget } from '@theia/core/shared/@phosphor/widgets';
|
||||
import { Message } from '@theia/core/shared/@phosphor/messaging';
|
||||
import { Deferred } from '@theia/core/lib/common/promise-util';
|
||||
@ -12,7 +16,7 @@ import {
|
||||
Installable,
|
||||
Searchable,
|
||||
ArduinoComponent,
|
||||
ResponseServiceArduino,
|
||||
ResponseServiceClient,
|
||||
} from '../../../common/protocol';
|
||||
import { FilterableListContainer } from './filterable-list-container';
|
||||
import { ListItemRenderer } from './list-item-renderer';
|
||||
@ -21,15 +25,15 @@ import { NotificationCenter } from '../../notification-center';
|
||||
@injectable()
|
||||
export abstract class ListWidget<
|
||||
T extends ArduinoComponent
|
||||
> extends ReactWidget {
|
||||
> extends ReactWidget {
|
||||
@inject(MessageService)
|
||||
protected readonly messageService: MessageService;
|
||||
|
||||
@inject(CommandService)
|
||||
protected readonly commandService: CommandService;
|
||||
|
||||
@inject(ResponseServiceArduino)
|
||||
protected readonly responseService: ResponseServiceArduino;
|
||||
@inject(ResponseServiceClient)
|
||||
protected readonly responseService: ResponseServiceClient;
|
||||
|
||||
@inject(NotificationCenter)
|
||||
protected readonly notificationCenter: NotificationCenter;
|
||||
@ -67,9 +71,9 @@ export abstract class ListWidget<
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this.toDispose.pushAll([
|
||||
this.notificationCenter.onIndexUpdated(() => this.refresh(undefined)),
|
||||
this.notificationCenter.onDaemonStarted(() => this.refresh(undefined)),
|
||||
this.notificationCenter.onDaemonStopped(() => this.refresh(undefined)),
|
||||
this.notificationCenter.onIndexDidUpdate(() => this.refresh(undefined)),
|
||||
this.notificationCenter.onDaemonDidStart(() => this.refresh(undefined)),
|
||||
this.notificationCenter.onDaemonDidStop(() => this.refresh(undefined)),
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -12,4 +12,7 @@ export interface ArduinoDaemon {
|
||||
* Otherwise resolves to the CLI daemon port.
|
||||
*/
|
||||
tryGetPort(): Promise<string | undefined>;
|
||||
start(): Promise<string>;
|
||||
stop(): Promise<void>;
|
||||
restart(): Promise<string>;
|
||||
}
|
||||
|
@ -1,10 +1,13 @@
|
||||
import { ApplicationError } from '@theia/core';
|
||||
import { Location } from '@theia/core/shared/vscode-languageserver-protocol';
|
||||
import { BoardUserField } from '.';
|
||||
import { Board, Port } from '../../common/protocol/boards-service';
|
||||
import { ErrorInfo as CliErrorInfo } from '../../node/cli-error-parser';
|
||||
import { Programmer } from './boards-service';
|
||||
import { Sketch } from './sketches-service';
|
||||
import { ApplicationError } from '@theia/core/lib/common/application-error';
|
||||
import type { Location } from '@theia/core/shared/vscode-languageserver-protocol';
|
||||
import type {
|
||||
Board,
|
||||
BoardUserField,
|
||||
Port,
|
||||
} from '../../common/protocol/boards-service';
|
||||
import type { ErrorInfo as CliErrorInfo } from '../../node/cli-error-parser';
|
||||
import type { Programmer } from './boards-service';
|
||||
import type { Sketch } from './sketches-service';
|
||||
|
||||
export const CompilerWarningLiterals = [
|
||||
'None',
|
||||
|
@ -1,13 +1,13 @@
|
||||
import * as semver from 'semver';
|
||||
import { Progress } from '@theia/core/lib/common/message-service-protocol';
|
||||
import type { Progress } from '@theia/core/lib/common/message-service-protocol';
|
||||
import {
|
||||
CancellationToken,
|
||||
CancellationTokenSource,
|
||||
} from '@theia/core/lib/common/cancellation';
|
||||
import { naturalCompare } from './../utils';
|
||||
import { ArduinoComponent } from './arduino-component';
|
||||
import { MessageService } from '@theia/core';
|
||||
import { ResponseServiceArduino } from './response-service';
|
||||
import type { ArduinoComponent } from './arduino-component';
|
||||
import type { MessageService } from '@theia/core/lib/common/message-service';
|
||||
import type { ResponseServiceClient } from './response-service';
|
||||
|
||||
export interface Installable<T extends ArduinoComponent> {
|
||||
/**
|
||||
@ -44,7 +44,7 @@ export namespace Installable {
|
||||
>(options: {
|
||||
installable: Installable<T>;
|
||||
messageService: MessageService;
|
||||
responseService: ResponseServiceArduino;
|
||||
responseService: ResponseServiceClient;
|
||||
item: T;
|
||||
version: Installable.Version;
|
||||
}): Promise<void> {
|
||||
@ -66,7 +66,7 @@ export namespace Installable {
|
||||
>(options: {
|
||||
installable: Installable<T>;
|
||||
messageService: MessageService;
|
||||
responseService: ResponseServiceArduino;
|
||||
responseService: ResponseServiceClient;
|
||||
item: T;
|
||||
}): Promise<void> {
|
||||
const { item } = options;
|
||||
@ -86,7 +86,7 @@ export namespace Installable {
|
||||
export async function doWithProgress(options: {
|
||||
run: ({ progressId }: { progressId: string }) => Promise<void>;
|
||||
messageService: MessageService;
|
||||
responseService: ResponseServiceArduino;
|
||||
responseService: ResponseServiceClient;
|
||||
progressText: string;
|
||||
}): Promise<void> {
|
||||
return withProgress(
|
||||
@ -103,7 +103,7 @@ export namespace Installable {
|
||||
}
|
||||
);
|
||||
try {
|
||||
options.responseService.clearArduinoChannel();
|
||||
options.responseService.clearOutput();
|
||||
await options.run({ progressId });
|
||||
} finally {
|
||||
toDispose.dispose();
|
||||
|
@ -1,23 +1,33 @@
|
||||
import { LibraryPackage } from './library-service';
|
||||
import { JsonRpcServer } from '@theia/core/lib/common/messaging/proxy-factory';
|
||||
import {
|
||||
Sketch,
|
||||
Config,
|
||||
BoardsPackage,
|
||||
import type { JsonRpcServer } from '@theia/core/lib/common/messaging/proxy-factory';
|
||||
import type {
|
||||
AttachedBoardsChangeEvent,
|
||||
BoardsPackage,
|
||||
Config,
|
||||
ProgressMessage,
|
||||
Sketch,
|
||||
} from '../protocol';
|
||||
import type { LibraryPackage } from './library-service';
|
||||
|
||||
export interface NotificationServiceClient {
|
||||
notifyIndexUpdated(): void;
|
||||
notifyDaemonStarted(port: string): void;
|
||||
notifyDaemonStopped(): void;
|
||||
notifyConfigChanged(event: { config: Config | undefined }): void;
|
||||
notifyPlatformInstalled(event: { item: BoardsPackage }): void;
|
||||
notifyPlatformUninstalled(event: { item: BoardsPackage }): void;
|
||||
notifyLibraryInstalled(event: { item: LibraryPackage }): void;
|
||||
notifyLibraryUninstalled(event: { item: LibraryPackage }): void;
|
||||
notifyAttachedBoardsChanged(event: AttachedBoardsChangeEvent): void;
|
||||
notifyRecentSketchesChanged(event: { sketches: Sketch[] }): void;
|
||||
notifyIndexWillUpdate(progressId: string): void;
|
||||
notifyIndexUpdateDidProgress(progressMessage: ProgressMessage): void;
|
||||
notifyIndexDidUpdate(progressId: string): void;
|
||||
notifyIndexUpdateDidFail({
|
||||
progressId,
|
||||
message,
|
||||
}: {
|
||||
progressId: string;
|
||||
message: string;
|
||||
}): void;
|
||||
notifyDaemonDidStart(port: string): void;
|
||||
notifyDaemonDidStop(): void;
|
||||
notifyConfigDidChange(event: { config: Config | undefined }): void;
|
||||
notifyPlatformDidInstall(event: { item: BoardsPackage }): void;
|
||||
notifyPlatformDidUninstall(event: { item: BoardsPackage }): void;
|
||||
notifyLibraryDidInstall(event: { item: LibraryPackage }): void;
|
||||
notifyLibraryDidUninstall(event: { item: LibraryPackage }): void;
|
||||
notifyAttachedBoardsDidChange(event: AttachedBoardsChangeEvent): void;
|
||||
notifyRecentSketchesDidChange(event: { sketches: Sketch[] }): void;
|
||||
}
|
||||
|
||||
export const NotificationServicePath = '/services/notification-service';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Event } from '@theia/core/lib/common/event';
|
||||
import type { Event } from '@theia/core/lib/common/event';
|
||||
|
||||
export interface OutputMessage {
|
||||
readonly chunk: string;
|
||||
@ -18,6 +18,18 @@ export interface ProgressMessage {
|
||||
readonly work?: ProgressMessage.Work;
|
||||
}
|
||||
export namespace ProgressMessage {
|
||||
export function is(arg: unknown): arg is ProgressMessage {
|
||||
if (typeof arg === 'object') {
|
||||
const object = arg as Record<string, unknown>;
|
||||
return (
|
||||
'progressId' in object &&
|
||||
typeof object.progressId === 'string' &&
|
||||
'message' in object &&
|
||||
typeof object.message === 'string'
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
export interface Work {
|
||||
readonly done: number;
|
||||
readonly total: number;
|
||||
@ -31,8 +43,8 @@ export interface ResponseService {
|
||||
reportProgress(message: ProgressMessage): void;
|
||||
}
|
||||
|
||||
export const ResponseServiceArduino = Symbol('ResponseServiceArduino');
|
||||
export interface ResponseServiceArduino extends ResponseService {
|
||||
export const ResponseServiceClient = Symbol('ResponseServiceClient');
|
||||
export interface ResponseServiceClient extends ResponseService {
|
||||
onProgressDidChange: Event<ProgressMessage>;
|
||||
clearArduinoChannel: () => void;
|
||||
clearOutput: () => void;
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import { inject, injectable, named } from '@theia/core/shared/inversify';
|
||||
import { spawn, ChildProcess } from 'child_process';
|
||||
import { FileUri } from '@theia/core/lib/node/file-uri';
|
||||
import { ILogger } from '@theia/core/lib/common/logger';
|
||||
import { Deferred } from '@theia/core/lib/common/promise-util';
|
||||
import { Deferred, retry } from '@theia/core/lib/common/promise-util';
|
||||
import {
|
||||
Disposable,
|
||||
DisposableCollection,
|
||||
@ -23,26 +23,26 @@ export class ArduinoDaemonImpl
|
||||
{
|
||||
@inject(ILogger)
|
||||
@named('daemon')
|
||||
protected readonly logger: ILogger;
|
||||
private readonly logger: ILogger;
|
||||
|
||||
@inject(EnvVariablesServer)
|
||||
protected readonly envVariablesServer: EnvVariablesServer;
|
||||
private readonly envVariablesServer: EnvVariablesServer;
|
||||
|
||||
@inject(NotificationServiceServer)
|
||||
protected readonly notificationService: NotificationServiceServer;
|
||||
private readonly notificationService: NotificationServiceServer;
|
||||
|
||||
protected readonly toDispose = new DisposableCollection();
|
||||
protected readonly onDaemonStartedEmitter = new Emitter<string>();
|
||||
protected readonly onDaemonStoppedEmitter = new Emitter<void>();
|
||||
private readonly toDispose = new DisposableCollection();
|
||||
private readonly onDaemonStartedEmitter = new Emitter<string>();
|
||||
private readonly onDaemonStoppedEmitter = new Emitter<void>();
|
||||
|
||||
protected _running = false;
|
||||
protected _port = new Deferred<string>();
|
||||
protected _execPath: string | undefined;
|
||||
private _running = false;
|
||||
private _port = new Deferred<string>();
|
||||
private _execPath: string | undefined;
|
||||
|
||||
// Backend application lifecycle.
|
||||
|
||||
onStart(): void {
|
||||
this.startDaemon(); // no await
|
||||
this.start(); // no await
|
||||
}
|
||||
|
||||
// Daemon API
|
||||
@ -58,7 +58,7 @@ export class ArduinoDaemonImpl
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async startDaemon(): Promise<void> {
|
||||
async start(): Promise<string> {
|
||||
try {
|
||||
this.toDispose.dispose(); // This will `kill` the previously started daemon process, if any.
|
||||
const cliPath = await this.getExecPath();
|
||||
@ -86,24 +86,29 @@ export class ArduinoDaemonImpl
|
||||
]);
|
||||
this.fireDaemonStarted(port);
|
||||
this.onData('Daemon is running.');
|
||||
return port;
|
||||
} catch (err) {
|
||||
this.onData('Failed to start the daemon.');
|
||||
this.onError(err);
|
||||
let i = 5; // TODO: make this better
|
||||
while (i) {
|
||||
this.onData(`Restarting daemon in ${i} seconds...`);
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
i--;
|
||||
}
|
||||
this.onData('Restarting daemon now...');
|
||||
return this.startDaemon();
|
||||
return retry(
|
||||
() => {
|
||||
this.onError(err);
|
||||
return this.start();
|
||||
},
|
||||
1_000,
|
||||
5
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async stopDaemon(): Promise<void> {
|
||||
async stop(): Promise<void> {
|
||||
this.toDispose.dispose();
|
||||
}
|
||||
|
||||
async restart(): Promise<string> {
|
||||
return this.start();
|
||||
}
|
||||
|
||||
// Backend only daemon API
|
||||
|
||||
get onDaemonStarted(): Event<string> {
|
||||
return this.onDaemonStartedEmitter.event;
|
||||
}
|
||||
@ -275,14 +280,14 @@ export class ArduinoDaemonImpl
|
||||
return ready.promise;
|
||||
}
|
||||
|
||||
protected fireDaemonStarted(port: string): void {
|
||||
private fireDaemonStarted(port: string): void {
|
||||
this._running = true;
|
||||
this._port.resolve(port);
|
||||
this.onDaemonStartedEmitter.fire(port);
|
||||
this.notificationService.notifyDaemonStarted(port);
|
||||
this.notificationService.notifyDaemonDidStart(port);
|
||||
}
|
||||
|
||||
protected fireDaemonStopped(): void {
|
||||
private fireDaemonStopped(): void {
|
||||
if (!this._running) {
|
||||
return;
|
||||
}
|
||||
@ -290,14 +295,15 @@ export class ArduinoDaemonImpl
|
||||
this._port.reject(); // Reject all pending.
|
||||
this._port = new Deferred<string>();
|
||||
this.onDaemonStoppedEmitter.fire();
|
||||
this.notificationService.notifyDaemonStopped();
|
||||
this.notificationService.notifyDaemonDidStop();
|
||||
}
|
||||
|
||||
protected onData(message: string): void {
|
||||
this.logger.info(message);
|
||||
}
|
||||
|
||||
protected onError(error: any): void {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
private onError(error: any): void {
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ import {
|
||||
BoardsService,
|
||||
BoardsServicePath,
|
||||
} from '../common/protocol/boards-service';
|
||||
import { LibraryServiceImpl } from './library-service-server-impl';
|
||||
import { LibraryServiceImpl } from './library-service-impl';
|
||||
import { BoardsServiceImpl } from './boards-service-impl';
|
||||
import { CoreServiceImpl } from './core-service-impl';
|
||||
import { CoreService, CoreServicePath } from '../common/protocol/core-service';
|
||||
@ -245,7 +245,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
const webSocketProvider =
|
||||
container.get<WebSocketProvider>(WebSocketProvider);
|
||||
|
||||
const { board, port, coreClientProvider, monitorID } = options;
|
||||
const { board, port, monitorID } = options;
|
||||
|
||||
return new MonitorService(
|
||||
logger,
|
||||
@ -253,8 +253,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
webSocketProvider,
|
||||
board,
|
||||
port,
|
||||
monitorID,
|
||||
coreClientProvider
|
||||
monitorID
|
||||
);
|
||||
}
|
||||
);
|
||||
|
@ -55,7 +55,7 @@ export class BoardDiscovery extends CoreClientAware {
|
||||
|
||||
@postConstruct()
|
||||
protected async init(): Promise<void> {
|
||||
this.coreClient().then((client) => this.startBoardListWatch(client));
|
||||
this.coreClient.then((client) => this.startBoardListWatch(client));
|
||||
}
|
||||
|
||||
stopBoardListWatch(coreClient: CoreClientProvider.Client): Promise<void> {
|
||||
@ -181,7 +181,7 @@ export class BoardDiscovery extends CoreClientAware {
|
||||
};
|
||||
|
||||
this._state = newState;
|
||||
this.notificationService.notifyAttachedBoardsChanged(event);
|
||||
this.notificationService.notifyAttachedBoardsDidChange(event);
|
||||
}
|
||||
});
|
||||
this.boardWatchDuplex.write(req);
|
||||
|
@ -40,7 +40,7 @@ import {
|
||||
SupportedUserFieldsRequest,
|
||||
SupportedUserFieldsResponse,
|
||||
} from './cli-protocol/cc/arduino/cli/commands/v1/upload_pb';
|
||||
import { InstallWithProgress } from './grpc-installable';
|
||||
import { ExecuteWithProgress } from './grpc-progressible';
|
||||
|
||||
@injectable()
|
||||
export class BoardsServiceImpl
|
||||
@ -78,8 +78,7 @@ export class BoardsServiceImpl
|
||||
async getBoardDetails(options: {
|
||||
fqbn: string;
|
||||
}): Promise<BoardDetails | undefined> {
|
||||
await this.coreClientProvider.initialized;
|
||||
const coreClient = await this.coreClient();
|
||||
const coreClient = await this.coreClient;
|
||||
const { client, instance } = coreClient;
|
||||
const { fqbn } = options;
|
||||
const detailsReq = new BoardDetailsRequest();
|
||||
@ -218,8 +217,7 @@ export class BoardsServiceImpl
|
||||
}: {
|
||||
query?: string;
|
||||
}): Promise<BoardWithPackage[]> {
|
||||
await this.coreClientProvider.initialized;
|
||||
const { instance, client } = await this.coreClient();
|
||||
const { instance, client } = await this.coreClient;
|
||||
const req = new BoardSearchRequest();
|
||||
req.setSearchArgs(query || '');
|
||||
req.setInstance(instance);
|
||||
@ -252,8 +250,7 @@ export class BoardsServiceImpl
|
||||
fqbn: string;
|
||||
protocol: string;
|
||||
}): Promise<BoardUserField[]> {
|
||||
await this.coreClientProvider.initialized;
|
||||
const coreClient = await this.coreClient();
|
||||
const coreClient = await this.coreClient;
|
||||
const { client, instance } = coreClient;
|
||||
|
||||
const supportedUserFieldsReq = new SupportedUserFieldsRequest();
|
||||
@ -279,8 +276,7 @@ export class BoardsServiceImpl
|
||||
}
|
||||
|
||||
async search(options: { query?: string }): Promise<BoardsPackage[]> {
|
||||
await this.coreClientProvider.initialized;
|
||||
const coreClient = await this.coreClient();
|
||||
const coreClient = await this.coreClient;
|
||||
const { client, instance } = coreClient;
|
||||
|
||||
const installedPlatformsReq = new PlatformListRequest();
|
||||
@ -404,8 +400,7 @@ export class BoardsServiceImpl
|
||||
const version = !!options.version
|
||||
? options.version
|
||||
: item.availableVersions[0];
|
||||
await this.coreClientProvider.initialized;
|
||||
const coreClient = await this.coreClient();
|
||||
const coreClient = await this.coreClient;
|
||||
const { client, instance } = coreClient;
|
||||
|
||||
const [platform, architecture] = item.id.split(':');
|
||||
@ -424,7 +419,7 @@ export class BoardsServiceImpl
|
||||
const resp = client.platformInstall(req);
|
||||
resp.on(
|
||||
'data',
|
||||
InstallWithProgress.createDataCallback({
|
||||
ExecuteWithProgress.createDataCallback({
|
||||
progressId: options.progressId,
|
||||
responseService: this.responseService,
|
||||
})
|
||||
@ -448,7 +443,7 @@ export class BoardsServiceImpl
|
||||
const items = await this.search({});
|
||||
const updated =
|
||||
items.find((other) => BoardsPackage.equals(other, item)) || item;
|
||||
this.notificationService.notifyPlatformInstalled({ item: updated });
|
||||
this.notificationService.notifyPlatformDidInstall({ item: updated });
|
||||
console.info('<<< Boards package installation done.', item);
|
||||
}
|
||||
|
||||
@ -457,8 +452,7 @@ export class BoardsServiceImpl
|
||||
progressId?: string;
|
||||
}): Promise<void> {
|
||||
const { item, progressId } = options;
|
||||
await this.coreClientProvider.initialized;
|
||||
const coreClient = await this.coreClient();
|
||||
const coreClient = await this.coreClient;
|
||||
const { client, instance } = coreClient;
|
||||
|
||||
const [platform, architecture] = item.id.split(':');
|
||||
@ -476,7 +470,7 @@ export class BoardsServiceImpl
|
||||
const resp = client.platformUninstall(req);
|
||||
resp.on(
|
||||
'data',
|
||||
InstallWithProgress.createDataCallback({
|
||||
ExecuteWithProgress.createDataCallback({
|
||||
progressId,
|
||||
responseService: this.responseService,
|
||||
})
|
||||
@ -490,7 +484,7 @@ export class BoardsServiceImpl
|
||||
});
|
||||
|
||||
// Here, unlike at `install` we send out the argument `item`. Otherwise, we would not know about the board FQBN.
|
||||
this.notificationService.notifyPlatformUninstalled({ item });
|
||||
this.notificationService.notifyPlatformDidUninstall({ item });
|
||||
console.info('<<< Boards package uninstallation done.', item);
|
||||
}
|
||||
}
|
||||
|
@ -199,11 +199,11 @@ export class ConfigServiceImpl
|
||||
|
||||
protected fireConfigChanged(config: Config): void {
|
||||
this.configChangeEmitter.fire(config);
|
||||
this.notificationService.notifyConfigChanged({ config });
|
||||
this.notificationService.notifyConfigDidChange({ config });
|
||||
}
|
||||
|
||||
protected fireInvalidConfig(): void {
|
||||
this.notificationService.notifyConfigChanged({ config: undefined });
|
||||
this.notificationService.notifyConfigDidChange({ config: undefined });
|
||||
}
|
||||
|
||||
protected async updateDaemon(
|
||||
|
@ -1,18 +1,18 @@
|
||||
import { join } from 'path';
|
||||
import * as grpc from '@grpc/grpc-js';
|
||||
import {
|
||||
inject,
|
||||
injectable,
|
||||
postConstruct,
|
||||
} from '@theia/core/shared/inversify';
|
||||
import { Event, Emitter } from '@theia/core/lib/common/event';
|
||||
import { GrpcClientProvider } from './grpc-client-provider';
|
||||
import { Emitter } from '@theia/core/lib/common/event';
|
||||
import { ArduinoCoreServiceClient } from './cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb';
|
||||
import { Instance } from './cli-protocol/cc/arduino/cli/commands/v1/common_pb';
|
||||
import {
|
||||
CreateRequest,
|
||||
CreateResponse,
|
||||
InitRequest,
|
||||
InitResponse,
|
||||
UpdateCoreLibrariesIndexResponse,
|
||||
UpdateIndexRequest,
|
||||
UpdateIndexResponse,
|
||||
UpdateLibrariesIndexRequest,
|
||||
@ -25,263 +25,363 @@ import {
|
||||
Status as RpcStatus,
|
||||
Status,
|
||||
} from './cli-protocol/google/rpc/status_pb';
|
||||
import { ConfigServiceImpl } from './config-service-impl';
|
||||
import { ArduinoDaemonImpl } from './arduino-daemon-impl';
|
||||
import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { Disposable } from '@theia/core/shared/vscode-languageserver-protocol';
|
||||
import {
|
||||
IndexesUpdateProgressHandler,
|
||||
ExecuteWithProgress,
|
||||
} from './grpc-progressible';
|
||||
import type { DefaultCliConfig } from './cli-config';
|
||||
import { ServiceError } from './service-error';
|
||||
|
||||
@injectable()
|
||||
export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Client> {
|
||||
export class CoreClientProvider {
|
||||
@inject(ArduinoDaemonImpl)
|
||||
private readonly daemon: ArduinoDaemonImpl;
|
||||
@inject(ConfigServiceImpl)
|
||||
private readonly configService: ConfigServiceImpl;
|
||||
@inject(NotificationServiceServer)
|
||||
protected readonly notificationService: NotificationServiceServer;
|
||||
private readonly notificationService: NotificationServiceServer;
|
||||
|
||||
protected readonly onClientReadyEmitter = new Emitter<void>();
|
||||
private ready = new Deferred<void>();
|
||||
private pending: Deferred<CoreClientProvider.Client> | undefined;
|
||||
private _client: CoreClientProvider.Client | undefined;
|
||||
private readonly toDisposeBeforeCreate = new DisposableCollection();
|
||||
private readonly toDisposeAfterDidCreate = new DisposableCollection();
|
||||
private readonly onClientReadyEmitter =
|
||||
new Emitter<CoreClientProvider.Client>();
|
||||
private readonly onClientReady = this.onClientReadyEmitter.event;
|
||||
|
||||
protected _created = new Deferred<void>();
|
||||
protected _initialized = new Deferred<void>();
|
||||
|
||||
get created(): Promise<void> {
|
||||
return this._created.promise;
|
||||
}
|
||||
|
||||
get initialized(): Promise<void> {
|
||||
return this._initialized.promise;
|
||||
}
|
||||
|
||||
get onClientReady(): Event<void> {
|
||||
return this.onClientReadyEmitter.event;
|
||||
}
|
||||
|
||||
close(client: CoreClientProvider.Client): void {
|
||||
client.client.close();
|
||||
this._created.reject();
|
||||
this._initialized.reject();
|
||||
this._created = new Deferred<void>();
|
||||
this._initialized = new Deferred<void>();
|
||||
}
|
||||
|
||||
protected override async reconcileClient(port: string): 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._client instanceof Error)) {
|
||||
await this.updateIndexes(this._client);
|
||||
this.onClientReadyEmitter.fire();
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this.daemon.tryGetPort().then((port) => {
|
||||
if (port) {
|
||||
this.create(port);
|
||||
}
|
||||
});
|
||||
this.daemon.onDaemonStarted((port) => this.create(port));
|
||||
this.daemon.onDaemonStopped(() => this.closeClient());
|
||||
this.configService.onConfigChange(() => this.refreshIndexes());
|
||||
}
|
||||
|
||||
get tryGetClient(): CoreClientProvider.Client | undefined {
|
||||
return this._client;
|
||||
}
|
||||
|
||||
get client(): Promise<CoreClientProvider.Client> {
|
||||
const client = this.tryGetClient;
|
||||
if (client) {
|
||||
return Promise.resolve(client);
|
||||
}
|
||||
if (!this.pending) {
|
||||
this.pending = new Deferred();
|
||||
this.toDisposeAfterDidCreate.pushAll([
|
||||
Disposable.create(() => (this.pending = undefined)),
|
||||
this.onClientReady((client) => {
|
||||
this.pending?.resolve(client);
|
||||
this.toDisposeAfterDidCreate.dispose();
|
||||
}),
|
||||
]);
|
||||
}
|
||||
return this.pending.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encapsulates both the gRPC core client creation (`CreateRequest`) and initialization (`InitRequest`).
|
||||
*/
|
||||
private async create(port: string): Promise<CoreClientProvider.Client> {
|
||||
this.closeClient();
|
||||
const address = this.address(port);
|
||||
const client = await this.createClient(address);
|
||||
this.toDisposeBeforeCreate.pushAll([
|
||||
Disposable.create(() => client.client.close()),
|
||||
Disposable.create(() => {
|
||||
this.ready.reject(
|
||||
new Error(
|
||||
`Disposed. Creating a new gRPC core client on address ${address}.`
|
||||
)
|
||||
);
|
||||
this.ready = new Deferred();
|
||||
}),
|
||||
]);
|
||||
await this.initInstanceWithFallback(client);
|
||||
setTimeout(async () => this.refreshIndexes(), 10_000); // Update the indexes asynchronously
|
||||
return this.useClient(client);
|
||||
}
|
||||
|
||||
/**
|
||||
* By default, calling this method is equivalent to the `initInstance(Client)` call.
|
||||
* When the IDE2 starts and one of the followings is missing,
|
||||
* the IDE2 must run the index update before the core client initialization:
|
||||
*
|
||||
* - primary package index (`#directories.data/package_index.json`),
|
||||
* - library index (`#directories.data/library_index.json`),
|
||||
* - built-in tools (`builtin:serial-discovery` or `builtin:mdns-discovery`)
|
||||
*
|
||||
* This method detects such errors and runs an index update before initializing the client.
|
||||
* The index update will fail if the 3rd URLs list contains an invalid URL,
|
||||
* and the IDE2 will be [non-functional](https://github.com/arduino/arduino-ide/issues/1084). Since the CLI [cannot update only the primary package index]((https://github.com/arduino/arduino-cli/issues/1788)), IDE2 does its dirty solution.
|
||||
*/
|
||||
private async initInstanceWithFallback(
|
||||
client: CoreClientProvider.Client
|
||||
): Promise<void> {
|
||||
try {
|
||||
await this.initInstance(client);
|
||||
} catch (err) {
|
||||
if (err instanceof IndexUpdateRequiredBeforeInitError) {
|
||||
console.error(
|
||||
'The primary packages indexes are missing. Running indexes update before initializing the core gRPC client',
|
||||
err.message
|
||||
);
|
||||
await this.updateIndexes(client); // TODO: this should run without the 3rd party URLs
|
||||
await this.initInstance(client);
|
||||
console.info(
|
||||
`Downloaded the primary packages indexes, and successfully initialized the core gRPC client.`
|
||||
);
|
||||
} else {
|
||||
console.error(
|
||||
'Error occurred while initializing the core gRPC client provider',
|
||||
err
|
||||
);
|
||||
throw err;
|
||||
}
|
||||
} else {
|
||||
await super.reconcileClient(port);
|
||||
this.onClientReadyEmitter.fire();
|
||||
}
|
||||
}
|
||||
|
||||
@postConstruct()
|
||||
protected override init(): void {
|
||||
this.daemon.getPort().then(async (port) => {
|
||||
// First create the client and the instance synchronously
|
||||
// and notify client is ready.
|
||||
// TODO: Creation failure should probably be handled here
|
||||
await this.reconcileClient(port); // create instance
|
||||
this._created.resolve();
|
||||
|
||||
// Normal startup workflow:
|
||||
// 1. create instance,
|
||||
// 2. init instance,
|
||||
// 3. update indexes asynchronously.
|
||||
|
||||
// First startup workflow:
|
||||
// 1. create instance,
|
||||
// 2. update indexes and wait,
|
||||
// 3. init instance.
|
||||
if (this._client && !(this._client instanceof Error)) {
|
||||
try {
|
||||
await this.initInstance(this._client); // init instance
|
||||
this._initialized.resolve();
|
||||
this.updateIndex(this._client); // Update the indexes asynchronously
|
||||
} catch (error: unknown) {
|
||||
console.error(
|
||||
'Error occurred while initializing the core gRPC client provider',
|
||||
error
|
||||
);
|
||||
if (error instanceof IndexUpdateRequiredBeforeInitError) {
|
||||
// If it's a first start, IDE2 must run index update before the init request.
|
||||
await this.updateIndexes(this._client);
|
||||
await this.initInstance(this._client);
|
||||
this._initialized.resolve();
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.daemon.onDaemonStopped(() => {
|
||||
if (this._client && !(this._client instanceof Error)) {
|
||||
this.close(this._client);
|
||||
}
|
||||
this._client = undefined;
|
||||
this._port = undefined;
|
||||
});
|
||||
private useClient(
|
||||
client: CoreClientProvider.Client
|
||||
): CoreClientProvider.Client {
|
||||
this._client = client;
|
||||
this.onClientReadyEmitter.fire(this._client);
|
||||
return this._client;
|
||||
}
|
||||
|
||||
protected async createClient(
|
||||
port: string | number
|
||||
private closeClient(): void {
|
||||
return this.toDisposeBeforeCreate.dispose();
|
||||
}
|
||||
|
||||
private async createClient(
|
||||
address: string
|
||||
): Promise<CoreClientProvider.Client> {
|
||||
// https://github.com/agreatfool/grpc_tools_node_protoc_ts/blob/master/doc/grpcjs_support.md#usage
|
||||
const ArduinoCoreServiceClient = grpc.makeClientConstructor(
|
||||
// @ts-expect-error: ignore
|
||||
commandsGrpcPb['cc.arduino.cli.commands.v1.ArduinoCoreService'],
|
||||
'ArduinoCoreServiceService'
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
) as any;
|
||||
const client = new ArduinoCoreServiceClient(
|
||||
`localhost:${port}`,
|
||||
address,
|
||||
grpc.credentials.createInsecure(),
|
||||
this.channelOptions
|
||||
) as ArduinoCoreServiceClient;
|
||||
|
||||
const createRes = await new Promise<CreateResponse>((resolve, reject) => {
|
||||
client.create(new CreateRequest(), (err, res: CreateResponse) => {
|
||||
const instance = await new Promise<Instance>((resolve, reject) => {
|
||||
client.create(new CreateRequest(), (err, resp) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve(res);
|
||||
const instance = resp.getInstance();
|
||||
if (!instance) {
|
||||
reject(
|
||||
new Error(
|
||||
'`CreateResponse` was OK, but the retrieved `instance` was `undefined`.'
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
resolve(instance);
|
||||
});
|
||||
});
|
||||
|
||||
const instance = createRes.getInstance();
|
||||
if (!instance) {
|
||||
throw new Error(
|
||||
'Could not retrieve instance from the initialize response.'
|
||||
);
|
||||
}
|
||||
|
||||
return { instance, client };
|
||||
}
|
||||
|
||||
protected async initInstance({
|
||||
private async initInstance({
|
||||
client,
|
||||
instance,
|
||||
}: CoreClientProvider.Client): Promise<void> {
|
||||
const initReq = new InitRequest();
|
||||
initReq.setInstance(instance);
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const stream = client.init(initReq);
|
||||
const errors: RpcStatus[] = [];
|
||||
stream.on('data', (res: InitResponse) => {
|
||||
const progress = res.getInitProgress();
|
||||
if (progress) {
|
||||
const downloadProgress = progress.getDownloadProgress();
|
||||
if (downloadProgress && downloadProgress.getCompleted()) {
|
||||
const file = downloadProgress.getFile();
|
||||
console.log(`Downloaded ${file}`);
|
||||
client
|
||||
.init(new InitRequest().setInstance(instance))
|
||||
.on('data', (resp: InitResponse) => {
|
||||
// XXX: The CLI never sends `initProgress`, it's always `error` or nothing. Is this a CLI bug?
|
||||
// According to the gRPC API, the CLI should send either a `TaskProgress` or a `DownloadProgress`, but it does not.
|
||||
const error = resp.getError();
|
||||
if (error) {
|
||||
const { code, message } = Status.toObject(false, error);
|
||||
console.error(
|
||||
`Detected an error response during the gRPC core client initialization: code: ${code}, message: ${message}`
|
||||
);
|
||||
errors.push(error);
|
||||
}
|
||||
const taskProgress = progress.getTaskProgress();
|
||||
if (taskProgress && taskProgress.getCompleted()) {
|
||||
const name = taskProgress.getName();
|
||||
console.log(`Completed ${name}`);
|
||||
})
|
||||
.on('error', reject)
|
||||
.on('end', () => {
|
||||
const error = this.evaluateErrorStatus(errors);
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const error = res.getError();
|
||||
if (error) {
|
||||
const { code, message } = Status.toObject(false, error);
|
||||
console.error(
|
||||
`Detected an error response during the gRPC core client initialization: code: ${code}, message: ${message}`
|
||||
);
|
||||
errors.push(error);
|
||||
}
|
||||
});
|
||||
stream.on('error', reject);
|
||||
stream.on('end', () => {
|
||||
const error = this.evaluateErrorStatus(errors);
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private evaluateErrorStatus(status: RpcStatus[]): Error | undefined {
|
||||
const error = isIndexUpdateRequiredBeforeInit(status); // put future error matching here
|
||||
return error;
|
||||
const { cliConfiguration } = this.configService;
|
||||
if (!cliConfiguration) {
|
||||
// If the CLI config is not available, do not even try to guess what went wrong.
|
||||
return new Error(`Could not read the CLI configuration file.`);
|
||||
}
|
||||
return isIndexUpdateRequiredBeforeInit(status, cliConfiguration); // put future error matching here
|
||||
}
|
||||
|
||||
protected async updateIndexes(
|
||||
client: CoreClientProvider.Client
|
||||
): Promise<CoreClientProvider.Client> {
|
||||
/**
|
||||
* Updates all indexes and runs an init to [reload the indexes](https://github.com/arduino/arduino-cli/pull/1274#issue-866154638).
|
||||
*/
|
||||
private async refreshIndexes(): Promise<void> {
|
||||
const client = this._client;
|
||||
if (client) {
|
||||
const progressHandler = this.createProgressHandler();
|
||||
try {
|
||||
await this.updateIndexes(client, progressHandler);
|
||||
await this.initInstance(client);
|
||||
// notify clients about the index update only after the client has been "re-initialized" and the new content is available.
|
||||
progressHandler.reportEnd();
|
||||
} catch (err) {
|
||||
console.error('Failed to update indexes', err);
|
||||
progressHandler.reportError(
|
||||
ServiceError.is(err) ? err.details : String(err)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async updateIndexes(
|
||||
client: CoreClientProvider.Client,
|
||||
progressHandler?: IndexesUpdateProgressHandler
|
||||
): Promise<void> {
|
||||
await Promise.all([
|
||||
retry(() => this.updateIndex(client), 50, 3),
|
||||
retry(() => this.updateLibraryIndex(client), 50, 3),
|
||||
this.updateIndex(client, progressHandler),
|
||||
this.updateLibraryIndex(client, progressHandler),
|
||||
]);
|
||||
this.notificationService.notifyIndexUpdated();
|
||||
return client;
|
||||
}
|
||||
|
||||
protected async updateLibraryIndex({
|
||||
client,
|
||||
instance,
|
||||
}: CoreClientProvider.Client): Promise<void> {
|
||||
const req = new UpdateLibrariesIndexRequest();
|
||||
req.setInstance(instance);
|
||||
const resp = client.updateLibrariesIndex(req);
|
||||
let file: string | undefined;
|
||||
resp.on('data', (data: UpdateLibrariesIndexResponse) => {
|
||||
const progress = data.getDownloadProgress();
|
||||
if (progress) {
|
||||
if (!file && progress.getFile()) {
|
||||
file = `${progress.getFile()}`;
|
||||
}
|
||||
if (progress.getCompleted()) {
|
||||
if (file) {
|
||||
if (/\s/.test(file)) {
|
||||
console.log(`${file} completed.`);
|
||||
} else {
|
||||
console.log(`Download of '${file}' completed.`);
|
||||
}
|
||||
} else {
|
||||
console.log('The library index has been successfully updated.');
|
||||
}
|
||||
file = undefined;
|
||||
}
|
||||
}
|
||||
});
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
resp.on('error', (error) => {
|
||||
reject(error);
|
||||
});
|
||||
resp.on('end', resolve);
|
||||
});
|
||||
private async updateIndex(
|
||||
client: CoreClientProvider.Client,
|
||||
progressHandler?: IndexesUpdateProgressHandler
|
||||
): Promise<void> {
|
||||
return this.doUpdateIndex(
|
||||
() =>
|
||||
client.client.updateIndex(
|
||||
new UpdateIndexRequest().setInstance(client.instance)
|
||||
),
|
||||
progressHandler,
|
||||
'platform-index'
|
||||
);
|
||||
}
|
||||
|
||||
protected async updateIndex({
|
||||
client,
|
||||
instance,
|
||||
}: CoreClientProvider.Client): Promise<void> {
|
||||
const updateReq = new UpdateIndexRequest();
|
||||
updateReq.setInstance(instance);
|
||||
const updateResp = client.updateIndex(updateReq);
|
||||
let file: string | undefined;
|
||||
updateResp.on('data', (o: UpdateIndexResponse) => {
|
||||
const progress = o.getDownloadProgress();
|
||||
if (progress) {
|
||||
if (!file && progress.getFile()) {
|
||||
file = `${progress.getFile()}`;
|
||||
}
|
||||
if (progress.getCompleted()) {
|
||||
if (file) {
|
||||
if (/\s/.test(file)) {
|
||||
console.log(`${file} completed.`);
|
||||
} else {
|
||||
console.log(`Download of '${file}' completed.`);
|
||||
}
|
||||
} else {
|
||||
console.log('The index has been successfully updated.');
|
||||
}
|
||||
file = undefined;
|
||||
}
|
||||
}
|
||||
});
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
updateResp.on('error', reject);
|
||||
updateResp.on('end', resolve);
|
||||
});
|
||||
private async updateLibraryIndex(
|
||||
client: CoreClientProvider.Client,
|
||||
progressHandler?: IndexesUpdateProgressHandler
|
||||
): Promise<void> {
|
||||
return this.doUpdateIndex(
|
||||
() =>
|
||||
client.client.updateLibrariesIndex(
|
||||
new UpdateLibrariesIndexRequest().setInstance(client.instance)
|
||||
),
|
||||
progressHandler,
|
||||
'library-index'
|
||||
);
|
||||
}
|
||||
|
||||
private async doUpdateIndex<
|
||||
R extends
|
||||
| UpdateIndexResponse
|
||||
| UpdateLibrariesIndexResponse
|
||||
| UpdateCoreLibrariesIndexResponse // not used by IDE2
|
||||
>(
|
||||
responseProvider: () => grpc.ClientReadableStream<R>,
|
||||
progressHandler?: IndexesUpdateProgressHandler,
|
||||
task?: string
|
||||
): Promise<void> {
|
||||
const progressId = progressHandler?.progressId;
|
||||
return retry(
|
||||
() =>
|
||||
new Promise<void>((resolve, reject) => {
|
||||
responseProvider()
|
||||
.on(
|
||||
'data',
|
||||
ExecuteWithProgress.createDataCallback({
|
||||
responseService: {
|
||||
appendToOutput: ({ chunk: message }) => {
|
||||
console.log(
|
||||
`core-client-provider${task ? ` [${task}]` : ''}`,
|
||||
message
|
||||
);
|
||||
progressHandler?.reportProgress(message);
|
||||
},
|
||||
},
|
||||
progressId,
|
||||
})
|
||||
)
|
||||
.on('error', reject)
|
||||
.on('end', resolve);
|
||||
}),
|
||||
50,
|
||||
3
|
||||
);
|
||||
}
|
||||
|
||||
private createProgressHandler(): IndexesUpdateProgressHandler {
|
||||
const additionalUrlsCount =
|
||||
this.configService.cliConfiguration?.board_manager?.additional_urls
|
||||
?.length ?? 0;
|
||||
return new IndexesUpdateProgressHandler(
|
||||
additionalUrlsCount,
|
||||
(progressMessage) =>
|
||||
this.notificationService.notifyIndexUpdateDidProgress(progressMessage),
|
||||
({ progressId, message }) =>
|
||||
this.notificationService.notifyIndexUpdateDidFail({
|
||||
progressId,
|
||||
message,
|
||||
}),
|
||||
(progressId) =>
|
||||
this.notificationService.notifyIndexWillUpdate(progressId),
|
||||
(progressId) => this.notificationService.notifyIndexDidUpdate(progressId)
|
||||
);
|
||||
}
|
||||
|
||||
private address(port: string): string {
|
||||
return `localhost:${port}`;
|
||||
}
|
||||
|
||||
private get channelOptions(): Record<string, unknown> {
|
||||
return {
|
||||
'grpc.max_send_message_length': 512 * 1024 * 1024,
|
||||
'grpc.max_receive_message_length': 512 * 1024 * 1024,
|
||||
'grpc.primary_user_agent': `arduino-ide/${this.version}`,
|
||||
};
|
||||
}
|
||||
|
||||
private _version: string | undefined;
|
||||
private get version(): string {
|
||||
if (this._version) {
|
||||
return this._version;
|
||||
}
|
||||
const json = require('../../package.json');
|
||||
if ('version' in json) {
|
||||
this._version = json.version;
|
||||
}
|
||||
if (!this._version) {
|
||||
this._version = '0.0.0';
|
||||
}
|
||||
return this._version;
|
||||
}
|
||||
}
|
||||
export namespace CoreClientProvider {
|
||||
@ -291,22 +391,18 @@ export namespace CoreClientProvider {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sugar for making the gRPC core client available for the concrete service classes.
|
||||
*/
|
||||
@injectable()
|
||||
export abstract class CoreClientAware {
|
||||
@inject(CoreClientProvider)
|
||||
protected readonly coreClientProvider: CoreClientProvider;
|
||||
|
||||
protected async coreClient(): Promise<CoreClientProvider.Client> {
|
||||
return await new Promise<CoreClientProvider.Client>(
|
||||
async (resolve, reject) => {
|
||||
const client = await this.coreClientProvider.client();
|
||||
if (client && client instanceof Error) {
|
||||
reject(client);
|
||||
} else if (client) {
|
||||
return resolve(client);
|
||||
}
|
||||
}
|
||||
);
|
||||
private readonly coreClientProvider: CoreClientProvider;
|
||||
/**
|
||||
* Returns with a promise that resolves when the core client is initialized and ready.
|
||||
*/
|
||||
protected get coreClient(): Promise<CoreClientProvider.Client> {
|
||||
return this.coreClientProvider.client;
|
||||
}
|
||||
}
|
||||
|
||||
@ -326,13 +422,14 @@ ${causes
|
||||
}
|
||||
|
||||
function isIndexUpdateRequiredBeforeInit(
|
||||
status: RpcStatus[]
|
||||
status: RpcStatus[],
|
||||
cliConfig: DefaultCliConfig
|
||||
): IndexUpdateRequiredBeforeInitError | undefined {
|
||||
const causes = status
|
||||
.filter((s) =>
|
||||
IndexUpdateRequiredBeforeInit.map((predicate) => predicate(s)).some(
|
||||
Boolean
|
||||
)
|
||||
IndexUpdateRequiredBeforeInit.map((predicate) =>
|
||||
predicate(s, cliConfig)
|
||||
).some(Boolean)
|
||||
)
|
||||
.map((s) => RpcStatus.toObject(false, s));
|
||||
return causes.length
|
||||
@ -343,9 +440,14 @@ const IndexUpdateRequiredBeforeInit = [
|
||||
isPackageIndexMissingStatus,
|
||||
isDiscoveryNotFoundStatus,
|
||||
];
|
||||
function isPackageIndexMissingStatus(status: RpcStatus): boolean {
|
||||
function isPackageIndexMissingStatus(
|
||||
status: RpcStatus,
|
||||
{ directories: { data } }: DefaultCliConfig
|
||||
): boolean {
|
||||
const predicate = ({ message }: RpcStatus.AsObject) =>
|
||||
message.includes('loading json index file');
|
||||
message.includes('loading json index file') &&
|
||||
(message.includes(join(data, 'package_index.json')) ||
|
||||
message.includes(join(data, 'library_index.json')));
|
||||
// https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/package_manager.go#L247
|
||||
return evaluate(status, predicate);
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
|
||||
compilerWarnings?: CompilerWarnings;
|
||||
}
|
||||
): Promise<void> {
|
||||
const coreClient = await this.coreClient();
|
||||
const coreClient = await this.coreClient;
|
||||
const { client, instance } = coreClient;
|
||||
const handler = this.createOnDataHandler();
|
||||
const request = this.compileRequest(options, instance);
|
||||
@ -158,7 +158,7 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
|
||||
): Promise<void> {
|
||||
await this.compile(Object.assign(options, { exportBinaries: false }));
|
||||
|
||||
const coreClient = await this.coreClient();
|
||||
const coreClient = await this.coreClient;
|
||||
const { client, instance } = coreClient;
|
||||
const request = this.uploadOrUploadUsingProgrammerRequest(
|
||||
options,
|
||||
@ -228,7 +228,7 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
|
||||
}
|
||||
|
||||
async burnBootloader(options: CoreService.Bootloader.Options): Promise<void> {
|
||||
const coreClient = await this.coreClient();
|
||||
const coreClient = await this.coreClient;
|
||||
const { client, instance } = coreClient;
|
||||
const handler = this.createOnDataHandler();
|
||||
const request = this.burnBootloaderRequest(options, instance);
|
||||
|
@ -1,79 +0,0 @@
|
||||
import {
|
||||
inject,
|
||||
injectable,
|
||||
postConstruct,
|
||||
} from '@theia/core/shared/inversify';
|
||||
import { ILogger } from '@theia/core/lib/common/logger';
|
||||
import { MaybePromise } from '@theia/core/lib/common/types';
|
||||
import { ConfigServiceImpl } from './config-service-impl';
|
||||
import { ArduinoDaemonImpl } from './arduino-daemon-impl';
|
||||
|
||||
@injectable()
|
||||
export abstract class GrpcClientProvider<C> {
|
||||
@inject(ILogger)
|
||||
protected readonly logger: ILogger;
|
||||
|
||||
@inject(ArduinoDaemonImpl)
|
||||
protected readonly daemon: ArduinoDaemonImpl;
|
||||
|
||||
@inject(ConfigServiceImpl)
|
||||
protected readonly configService: ConfigServiceImpl;
|
||||
|
||||
protected _port: string | undefined;
|
||||
protected _client: C | Error | undefined;
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this.configService.onConfigChange(() => {
|
||||
// Only reconcile the gRPC client if the port is known. Hence the CLI daemon is running.
|
||||
if (this._port) {
|
||||
this.reconcileClient(this._port);
|
||||
}
|
||||
});
|
||||
this.daemon.getPort().then((port) => this.reconcileClient(port));
|
||||
this.daemon.onDaemonStopped(() => {
|
||||
if (this._client && !(this._client instanceof Error)) {
|
||||
this.close(this._client);
|
||||
}
|
||||
this._client = undefined;
|
||||
this._port = undefined;
|
||||
});
|
||||
}
|
||||
|
||||
async client(): Promise<C | Error | undefined> {
|
||||
try {
|
||||
await this.daemon.getPort();
|
||||
return this._client;
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
protected async reconcileClient(port: string): Promise<void> {
|
||||
this._port = port;
|
||||
if (this._client && !(this._client instanceof Error)) {
|
||||
this.close(this._client);
|
||||
this._client = undefined;
|
||||
}
|
||||
try {
|
||||
const client = await this.createClient(this._port);
|
||||
this._client = client;
|
||||
} catch (error) {
|
||||
this.logger.error('Could not create client for gRPC.', error);
|
||||
this._client = error;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract createClient(port: string | number): MaybePromise<C>;
|
||||
|
||||
protected abstract close(client: C): void;
|
||||
|
||||
protected get channelOptions(): Record<string, unknown> {
|
||||
const pjson = require('../../package.json') || { version: '0.0.0' };
|
||||
return {
|
||||
'grpc.max_send_message_length': 512 * 1024 * 1024,
|
||||
'grpc.max_receive_message_length': 512 * 1024 * 1024,
|
||||
'grpc.primary_user_agent': `arduino-ide/${pjson.version}`,
|
||||
};
|
||||
}
|
||||
}
|
@ -1,99 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
278
arduino-ide-extension/src/node/grpc-progressible.ts
Normal file
278
arduino-ide-extension/src/node/grpc-progressible.ts
Normal file
@ -0,0 +1,278 @@
|
||||
import { v4 } from 'uuid';
|
||||
import {
|
||||
ProgressMessage,
|
||||
ResponseService,
|
||||
} from '../common/protocol/response-service';
|
||||
import {
|
||||
UpdateCoreLibrariesIndexResponse,
|
||||
UpdateIndexResponse,
|
||||
UpdateLibrariesIndexResponse,
|
||||
} from './cli-protocol/cc/arduino/cli/commands/v1/commands_pb';
|
||||
import {
|
||||
DownloadProgress,
|
||||
TaskProgress,
|
||||
} from './cli-protocol/cc/arduino/cli/commands/v1/common_pb';
|
||||
import {
|
||||
PlatformInstallResponse,
|
||||
PlatformUninstallResponse,
|
||||
} from './cli-protocol/cc/arduino/cli/commands/v1/core_pb';
|
||||
import {
|
||||
LibraryInstallResponse,
|
||||
LibraryUninstallResponse,
|
||||
ZipLibraryInstallResponse,
|
||||
} from './cli-protocol/cc/arduino/cli/commands/v1/lib_pb';
|
||||
|
||||
type LibraryProgressResponse =
|
||||
| LibraryInstallResponse
|
||||
| LibraryUninstallResponse
|
||||
| ZipLibraryInstallResponse;
|
||||
namespace LibraryProgressResponse {
|
||||
export function is(response: unknown): response is LibraryProgressResponse {
|
||||
return (
|
||||
response instanceof LibraryInstallResponse ||
|
||||
response instanceof LibraryUninstallResponse ||
|
||||
response instanceof ZipLibraryInstallResponse
|
||||
);
|
||||
}
|
||||
export function workUnit(response: LibraryProgressResponse): UnitOfWork {
|
||||
return {
|
||||
task: response.getTaskProgress(),
|
||||
...(response instanceof LibraryInstallResponse && {
|
||||
download: response.getProgress(),
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
type PlatformProgressResponse =
|
||||
| PlatformInstallResponse
|
||||
| PlatformUninstallResponse;
|
||||
namespace PlatformProgressResponse {
|
||||
export function is(response: unknown): response is PlatformProgressResponse {
|
||||
return (
|
||||
response instanceof PlatformInstallResponse ||
|
||||
response instanceof PlatformUninstallResponse
|
||||
);
|
||||
}
|
||||
export function workUnit(response: PlatformProgressResponse): UnitOfWork {
|
||||
return {
|
||||
task: response.getTaskProgress(),
|
||||
...(response instanceof PlatformInstallResponse && {
|
||||
download: response.getProgress(),
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
type IndexProgressResponse =
|
||||
| UpdateIndexResponse
|
||||
| UpdateLibrariesIndexResponse
|
||||
| UpdateCoreLibrariesIndexResponse;
|
||||
namespace IndexProgressResponse {
|
||||
export function is(response: unknown): response is IndexProgressResponse {
|
||||
return (
|
||||
response instanceof UpdateIndexResponse ||
|
||||
response instanceof UpdateLibrariesIndexResponse ||
|
||||
response instanceof UpdateCoreLibrariesIndexResponse // not used by the IDE2 but available for full typings compatibility
|
||||
);
|
||||
}
|
||||
export function workUnit(response: IndexProgressResponse): UnitOfWork {
|
||||
return { download: response.getDownloadProgress() };
|
||||
}
|
||||
}
|
||||
export type ProgressResponse =
|
||||
| LibraryProgressResponse
|
||||
| PlatformProgressResponse
|
||||
| IndexProgressResponse;
|
||||
|
||||
interface UnitOfWork {
|
||||
task?: TaskProgress;
|
||||
download?: DownloadProgress;
|
||||
}
|
||||
|
||||
/**
|
||||
* It's solely a dev thing. Flip it to `true` if you want to debug the progress from the CLI responses.
|
||||
*/
|
||||
const DEBUG = false;
|
||||
export namespace ExecuteWithProgress {
|
||||
export interface Options {
|
||||
/**
|
||||
* _unknown_ progress if falsy.
|
||||
*/
|
||||
readonly progressId?: string;
|
||||
readonly responseService: Partial<ResponseService>;
|
||||
}
|
||||
|
||||
export function createDataCallback<R extends ProgressResponse>({
|
||||
responseService,
|
||||
progressId,
|
||||
}: ExecuteWithProgress.Options): (response: R) => void {
|
||||
const uuid = v4();
|
||||
let localFile = '';
|
||||
let localTotalSize = Number.NaN;
|
||||
return (response: R) => {
|
||||
if (DEBUG) {
|
||||
const json = toJson(response);
|
||||
if (json) {
|
||||
console.log(`Progress response [${uuid}]: ${json}`);
|
||||
}
|
||||
}
|
||||
const { task, download } = resolve(response);
|
||||
if (!download && !task) {
|
||||
console.warn(
|
||||
"Implementation error. Neither 'download' nor 'task' is available."
|
||||
);
|
||||
// This is still an API error from the CLI, but IDE2 ignores it.
|
||||
// Technically, it does not cause an error, but could mess up the progress reporting.
|
||||
// See an example of an empty object `{}` repose here: https://github.com/arduino/arduino-ide/issues/906#issuecomment-1171145630.
|
||||
return;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
function resolve(response: unknown): Readonly<Partial<UnitOfWork>> {
|
||||
if (LibraryProgressResponse.is(response)) {
|
||||
return LibraryProgressResponse.workUnit(response);
|
||||
} else if (PlatformProgressResponse.is(response)) {
|
||||
return PlatformProgressResponse.workUnit(response);
|
||||
} else if (IndexProgressResponse.is(response)) {
|
||||
return IndexProgressResponse.workUnit(response);
|
||||
}
|
||||
console.warn('Unhandled gRPC response', response);
|
||||
return {};
|
||||
}
|
||||
function toJson(response: ProgressResponse): string | undefined {
|
||||
if (response instanceof LibraryInstallResponse) {
|
||||
return JSON.stringify(LibraryInstallResponse.toObject(false, response));
|
||||
} else if (response instanceof LibraryUninstallResponse) {
|
||||
return JSON.stringify(LibraryUninstallResponse.toObject(false, response));
|
||||
} else if (response instanceof ZipLibraryInstallResponse) {
|
||||
return JSON.stringify(
|
||||
ZipLibraryInstallResponse.toObject(false, response)
|
||||
);
|
||||
} else if (response instanceof PlatformInstallResponse) {
|
||||
return JSON.stringify(PlatformInstallResponse.toObject(false, response));
|
||||
} else if (response instanceof PlatformUninstallResponse) {
|
||||
return JSON.stringify(
|
||||
PlatformUninstallResponse.toObject(false, response)
|
||||
);
|
||||
} else if (response instanceof UpdateIndexResponse) {
|
||||
return JSON.stringify(UpdateIndexResponse.toObject(false, response));
|
||||
} else if (response instanceof UpdateLibrariesIndexResponse) {
|
||||
return JSON.stringify(
|
||||
UpdateLibrariesIndexResponse.toObject(false, response)
|
||||
);
|
||||
} else if (response instanceof UpdateCoreLibrariesIndexResponse) {
|
||||
return JSON.stringify(
|
||||
UpdateCoreLibrariesIndexResponse.toObject(false, response)
|
||||
);
|
||||
}
|
||||
console.warn('Unhandled gRPC response', response);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export class IndexesUpdateProgressHandler {
|
||||
private done = 0;
|
||||
private readonly total: number;
|
||||
readonly progressId: string;
|
||||
|
||||
constructor(
|
||||
additionalUrlsCount: number,
|
||||
private readonly onProgress: (progressMessage: ProgressMessage) => void,
|
||||
private readonly onError?: ({
|
||||
progressId,
|
||||
message,
|
||||
}: {
|
||||
progressId: string;
|
||||
message: string;
|
||||
}) => void,
|
||||
private readonly onStart?: (progressId: string) => void,
|
||||
private readonly onEnd?: (progressId: string) => void
|
||||
) {
|
||||
this.progressId = v4();
|
||||
this.total = IndexesUpdateProgressHandler.total(additionalUrlsCount);
|
||||
// Note: at this point, the IDE2 backend might not have any connected clients, so this notification is not delivered to anywhere
|
||||
// Hence, clients must handle gracefully when no `willUpdate` is received before any `didProgress`.
|
||||
this.onStart?.(this.progressId);
|
||||
}
|
||||
|
||||
reportEnd(): void {
|
||||
this.onEnd?.(this.progressId);
|
||||
}
|
||||
|
||||
reportProgress(message: string): void {
|
||||
this.onProgress({
|
||||
message,
|
||||
progressId: this.progressId,
|
||||
work: { total: this.total, done: ++this.done },
|
||||
});
|
||||
}
|
||||
|
||||
reportError(message: string): void {
|
||||
this.onError?.({ progressId: this.progressId, message });
|
||||
}
|
||||
|
||||
private static total(additionalUrlsCount: number): number {
|
||||
// +1 for the `package_index.tar.bz2` when updating the platform index.
|
||||
const totalPlatformIndexCount = additionalUrlsCount + 1;
|
||||
// The `library_index.json.gz` and `library_index.json.sig` when running the library index update.
|
||||
const totalLibraryIndexCount = 2;
|
||||
// +1 for the `initInstance` call after the index update (`reportEnd`)
|
||||
return totalPlatformIndexCount + totalLibraryIndexCount + 1;
|
||||
}
|
||||
}
|
@ -25,7 +25,7 @@ import { Installable } from '../common/protocol/installable';
|
||||
import { ILogger, notEmpty } from '@theia/core';
|
||||
import { FileUri } from '@theia/core/lib/node';
|
||||
import { ResponseService, NotificationServiceServer } from '../common/protocol';
|
||||
import { InstallWithProgress } from './grpc-installable';
|
||||
import { ExecuteWithProgress } from './grpc-progressible';
|
||||
|
||||
@injectable()
|
||||
export class LibraryServiceImpl
|
||||
@ -45,8 +45,7 @@ export class LibraryServiceImpl
|
||||
protected readonly notificationServer: NotificationServiceServer;
|
||||
|
||||
async search(options: { query?: string }): Promise<LibraryPackage[]> {
|
||||
await this.coreClientProvider.initialized;
|
||||
const coreClient = await this.coreClient();
|
||||
const coreClient = await this.coreClient;
|
||||
const { client, instance } = coreClient;
|
||||
|
||||
const listReq = new LibraryListRequest();
|
||||
@ -112,8 +111,7 @@ export class LibraryServiceImpl
|
||||
}: {
|
||||
fqbn?: string | undefined;
|
||||
}): Promise<LibraryPackage[]> {
|
||||
await this.coreClientProvider.initialized;
|
||||
const coreClient = await this.coreClient();
|
||||
const coreClient = await this.coreClient;
|
||||
const { client, instance } = coreClient;
|
||||
const req = new LibraryListRequest();
|
||||
req.setInstance(instance);
|
||||
@ -218,8 +216,7 @@ export class LibraryServiceImpl
|
||||
version: Installable.Version;
|
||||
filterSelf?: boolean;
|
||||
}): Promise<LibraryDependency[]> {
|
||||
await this.coreClientProvider.initialized;
|
||||
const coreClient = await this.coreClient();
|
||||
const coreClient = await this.coreClient;
|
||||
const { client, instance } = coreClient;
|
||||
const req = new LibraryResolveDependenciesRequest();
|
||||
req.setInstance(instance);
|
||||
@ -260,8 +257,7 @@ export class LibraryServiceImpl
|
||||
const version = !!options.version
|
||||
? options.version
|
||||
: item.availableVersions[0];
|
||||
await this.coreClientProvider.initialized;
|
||||
const coreClient = await this.coreClient();
|
||||
const coreClient = await this.coreClient;
|
||||
const { client, instance } = coreClient;
|
||||
|
||||
const req = new LibraryInstallRequest();
|
||||
@ -278,7 +274,7 @@ export class LibraryServiceImpl
|
||||
const resp = client.libraryInstall(req);
|
||||
resp.on(
|
||||
'data',
|
||||
InstallWithProgress.createDataCallback({
|
||||
ExecuteWithProgress.createDataCallback({
|
||||
progressId: options.progressId,
|
||||
responseService: this.responseService,
|
||||
})
|
||||
@ -304,7 +300,7 @@ export class LibraryServiceImpl
|
||||
const items = await this.search({});
|
||||
const updated =
|
||||
items.find((other) => LibraryPackage.equals(other, item)) || item;
|
||||
this.notificationServer.notifyLibraryInstalled({ item: updated });
|
||||
this.notificationServer.notifyLibraryDidInstall({ item: updated });
|
||||
console.info('<<< Library package installation done.', item);
|
||||
}
|
||||
|
||||
@ -317,8 +313,7 @@ export class LibraryServiceImpl
|
||||
progressId?: string;
|
||||
overwrite?: boolean;
|
||||
}): Promise<void> {
|
||||
await this.coreClientProvider.initialized;
|
||||
const coreClient = await this.coreClient();
|
||||
const coreClient = await this.coreClient;
|
||||
const { client, instance } = coreClient;
|
||||
const req = new ZipLibraryInstallRequest();
|
||||
req.setPath(FileUri.fsPath(zipUri));
|
||||
@ -333,7 +328,7 @@ export class LibraryServiceImpl
|
||||
const resp = client.zipLibraryInstall(req);
|
||||
resp.on(
|
||||
'data',
|
||||
InstallWithProgress.createDataCallback({
|
||||
ExecuteWithProgress.createDataCallback({
|
||||
progressId,
|
||||
responseService: this.responseService,
|
||||
})
|
||||
@ -352,8 +347,7 @@ export class LibraryServiceImpl
|
||||
progressId?: string;
|
||||
}): Promise<void> {
|
||||
const { item, progressId } = options;
|
||||
await this.coreClientProvider.initialized;
|
||||
const coreClient = await this.coreClient();
|
||||
const coreClient = await this.coreClient;
|
||||
const { client, instance } = coreClient;
|
||||
|
||||
const req = new LibraryUninstallRequest();
|
||||
@ -369,7 +363,7 @@ export class LibraryServiceImpl
|
||||
const resp = client.libraryUninstall(req);
|
||||
resp.on(
|
||||
'data',
|
||||
InstallWithProgress.createDataCallback({
|
||||
ExecuteWithProgress.createDataCallback({
|
||||
progressId,
|
||||
responseService: this.responseService,
|
||||
})
|
||||
@ -382,7 +376,7 @@ export class LibraryServiceImpl
|
||||
resp.on('error', reject);
|
||||
});
|
||||
|
||||
this.notificationServer.notifyLibraryUninstalled({ item });
|
||||
this.notificationServer.notifyLibraryDidUninstall({ item });
|
||||
console.info('<<< Library package uninstallation done.', item);
|
||||
}
|
||||
|
@ -317,7 +317,6 @@ export class MonitorManager extends CoreClientAware {
|
||||
board,
|
||||
port,
|
||||
monitorID,
|
||||
coreClientProvider: this.coreClientProvider,
|
||||
});
|
||||
this.monitorServices.set(monitorID, monitor);
|
||||
monitor.onDispose(
|
||||
|
@ -1,20 +1,13 @@
|
||||
import { Board, Port } from '../common/protocol';
|
||||
import { CoreClientProvider } from './core-client-provider';
|
||||
import { MonitorService } from './monitor-service';
|
||||
|
||||
export const MonitorServiceFactory = Symbol('MonitorServiceFactory');
|
||||
export interface MonitorServiceFactory {
|
||||
(options: {
|
||||
board: Board;
|
||||
port: Port;
|
||||
monitorID: string;
|
||||
coreClientProvider: CoreClientProvider;
|
||||
}): MonitorService;
|
||||
(options: { board: Board; port: Port; monitorID: string }): MonitorService;
|
||||
}
|
||||
|
||||
export interface MonitorServiceFactoryOptions {
|
||||
board: Board;
|
||||
port: Port;
|
||||
monitorID: string;
|
||||
coreClientProvider: CoreClientProvider;
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
MonitorRequest,
|
||||
MonitorResponse,
|
||||
} from './cli-protocol/cc/arduino/cli/commands/v1/monitor_pb';
|
||||
import { CoreClientAware, CoreClientProvider } from './core-client-provider';
|
||||
import { CoreClientAware } from './core-client-provider';
|
||||
import { WebSocketProvider } from './web-socket/web-socket-provider';
|
||||
import { Port as gRPCPort } from 'arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/port_pb';
|
||||
import {
|
||||
@ -77,8 +77,7 @@ export class MonitorService extends CoreClientAware implements Disposable {
|
||||
|
||||
private readonly board: Board,
|
||||
private readonly port: Port,
|
||||
private readonly monitorID: string,
|
||||
protected override readonly coreClientProvider: CoreClientProvider
|
||||
private readonly monitorID: string
|
||||
) {
|
||||
super();
|
||||
|
||||
@ -175,8 +174,7 @@ export class MonitorService extends CoreClientAware implements Disposable {
|
||||
},
|
||||
};
|
||||
|
||||
await this.coreClientProvider.initialized;
|
||||
const coreClient = await this.coreClient();
|
||||
const coreClient = await this.coreClient;
|
||||
|
||||
const { instance } = coreClient;
|
||||
const monitorRequest = new MonitorRequest();
|
||||
@ -224,7 +222,7 @@ export class MonitorService extends CoreClientAware implements Disposable {
|
||||
async createDuplex(): Promise<
|
||||
ClientDuplexStream<MonitorRequest, MonitorResponse>
|
||||
> {
|
||||
const coreClient = await this.coreClient();
|
||||
const coreClient = await this.coreClient;
|
||||
return coreClient.client.monitor();
|
||||
}
|
||||
|
||||
@ -404,8 +402,7 @@ export class MonitorService extends CoreClientAware implements Disposable {
|
||||
if (!this.duplex) {
|
||||
return Status.NOT_CONNECTED;
|
||||
}
|
||||
await this.coreClientProvider.initialized;
|
||||
const coreClient = await this.coreClient();
|
||||
const coreClient = await this.coreClient;
|
||||
const { instance } = coreClient;
|
||||
|
||||
const req = new MonitorRequest();
|
||||
@ -431,7 +428,7 @@ export class MonitorService extends CoreClientAware implements Disposable {
|
||||
return this.settings;
|
||||
}
|
||||
|
||||
// TODO: move this into MonitoSettingsProvider
|
||||
// TODO: move this into MonitorSettingsProvider
|
||||
/**
|
||||
* Returns the possible configurations used to connect a monitor
|
||||
* to the board specified by fqbn using the specified protocol
|
||||
@ -443,8 +440,7 @@ export class MonitorService extends CoreClientAware implements Disposable {
|
||||
protocol: string,
|
||||
fqbn: string
|
||||
): Promise<PluggableMonitorSettings> {
|
||||
await this.coreClientProvider.initialized;
|
||||
const coreClient = await this.coreClient();
|
||||
const coreClient = await this.coreClient;
|
||||
const { client, instance } = coreClient;
|
||||
const req = new EnumerateMonitorPortSettingsRequest();
|
||||
req.setInstance(instance);
|
||||
@ -512,8 +508,7 @@ export class MonitorService extends CoreClientAware implements Disposable {
|
||||
if (!this.duplex) {
|
||||
return Status.NOT_CONNECTED;
|
||||
}
|
||||
await this.coreClientProvider.initialized;
|
||||
const coreClient = await this.coreClient();
|
||||
const coreClient = await this.coreClient;
|
||||
const { instance } = coreClient;
|
||||
|
||||
const req = new MonitorRequest();
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import {
|
||||
import type {
|
||||
NotificationServiceServer,
|
||||
NotificationServiceClient,
|
||||
AttachedBoardsChangeEvent,
|
||||
@ -7,52 +7,79 @@ import {
|
||||
LibraryPackage,
|
||||
Config,
|
||||
Sketch,
|
||||
ProgressMessage,
|
||||
} from '../common/protocol';
|
||||
|
||||
@injectable()
|
||||
export class NotificationServiceServerImpl
|
||||
implements NotificationServiceServer
|
||||
{
|
||||
protected readonly clients: NotificationServiceClient[] = [];
|
||||
private readonly clients: NotificationServiceClient[] = [];
|
||||
|
||||
notifyIndexUpdated(): void {
|
||||
this.clients.forEach((client) => client.notifyIndexUpdated());
|
||||
notifyIndexWillUpdate(progressId: string): void {
|
||||
this.clients.forEach((client) => client.notifyIndexWillUpdate(progressId));
|
||||
}
|
||||
|
||||
notifyDaemonStarted(port: string): void {
|
||||
this.clients.forEach((client) => client.notifyDaemonStarted(port));
|
||||
notifyIndexUpdateDidProgress(progressMessage: ProgressMessage): void {
|
||||
this.clients.forEach((client) =>
|
||||
client.notifyIndexUpdateDidProgress(progressMessage)
|
||||
);
|
||||
}
|
||||
|
||||
notifyDaemonStopped(): void {
|
||||
this.clients.forEach((client) => client.notifyDaemonStopped());
|
||||
notifyIndexDidUpdate(progressId: string): void {
|
||||
this.clients.forEach((client) => client.notifyIndexDidUpdate(progressId));
|
||||
}
|
||||
|
||||
notifyPlatformInstalled(event: { item: BoardsPackage }): void {
|
||||
this.clients.forEach((client) => client.notifyPlatformInstalled(event));
|
||||
notifyIndexUpdateDidFail({
|
||||
progressId,
|
||||
message,
|
||||
}: {
|
||||
progressId: string;
|
||||
message: string;
|
||||
}): void {
|
||||
this.clients.forEach((client) =>
|
||||
client.notifyIndexUpdateDidFail({ progressId, message })
|
||||
);
|
||||
}
|
||||
|
||||
notifyPlatformUninstalled(event: { item: BoardsPackage }): void {
|
||||
this.clients.forEach((client) => client.notifyPlatformUninstalled(event));
|
||||
notifyDaemonDidStart(port: string): void {
|
||||
this.clients.forEach((client) => client.notifyDaemonDidStart(port));
|
||||
}
|
||||
|
||||
notifyLibraryInstalled(event: { item: LibraryPackage }): void {
|
||||
this.clients.forEach((client) => client.notifyLibraryInstalled(event));
|
||||
notifyDaemonDidStop(): void {
|
||||
this.clients.forEach((client) => client.notifyDaemonDidStop());
|
||||
}
|
||||
|
||||
notifyLibraryUninstalled(event: { item: LibraryPackage }): void {
|
||||
this.clients.forEach((client) => client.notifyLibraryUninstalled(event));
|
||||
notifyPlatformDidInstall(event: { item: BoardsPackage }): void {
|
||||
this.clients.forEach((client) => client.notifyPlatformDidInstall(event));
|
||||
}
|
||||
|
||||
notifyAttachedBoardsChanged(event: AttachedBoardsChangeEvent): void {
|
||||
this.clients.forEach((client) => client.notifyAttachedBoardsChanged(event));
|
||||
notifyPlatformDidUninstall(event: { item: BoardsPackage }): void {
|
||||
this.clients.forEach((client) => client.notifyPlatformDidUninstall(event));
|
||||
}
|
||||
|
||||
notifyConfigChanged(event: { config: Config | undefined }): void {
|
||||
this.clients.forEach((client) => client.notifyConfigChanged(event));
|
||||
notifyLibraryDidInstall(event: { item: LibraryPackage }): void {
|
||||
this.clients.forEach((client) => client.notifyLibraryDidInstall(event));
|
||||
}
|
||||
|
||||
notifyRecentSketchesChanged(event: { sketches: Sketch[] }): void {
|
||||
this.clients.forEach((client) => client.notifyRecentSketchesChanged(event));
|
||||
notifyLibraryDidUninstall(event: { item: LibraryPackage }): void {
|
||||
this.clients.forEach((client) => client.notifyLibraryDidUninstall(event));
|
||||
}
|
||||
|
||||
notifyAttachedBoardsDidChange(event: AttachedBoardsChangeEvent): void {
|
||||
this.clients.forEach((client) =>
|
||||
client.notifyAttachedBoardsDidChange(event)
|
||||
);
|
||||
}
|
||||
|
||||
notifyConfigDidChange(event: { config: Config | undefined }): void {
|
||||
this.clients.forEach((client) => client.notifyConfigDidChange(event));
|
||||
}
|
||||
|
||||
notifyRecentSketchesDidChange(event: { sketches: Sketch[] }): void {
|
||||
this.clients.forEach((client) =>
|
||||
client.notifyRecentSketchesDidChange(event)
|
||||
);
|
||||
}
|
||||
|
||||
setClient(client: NotificationServiceClient): void {
|
||||
|
@ -189,7 +189,7 @@ export class SketchesServiceImpl
|
||||
}
|
||||
|
||||
async loadSketch(uri: string): Promise<SketchWithDetails> {
|
||||
const { client, instance } = await this.coreClient();
|
||||
const { client, instance } = await this.coreClient;
|
||||
const req = new LoadSketchRequest();
|
||||
const requestSketchPath = FileUri.fsPath(uri);
|
||||
req.setSketchPath(requestSketchPath);
|
||||
@ -295,7 +295,7 @@ export class SketchesServiceImpl
|
||||
|
||||
await promisify(fs.writeFile)(fsPath, JSON.stringify(data, null, 2));
|
||||
this.recentlyOpenedSketches().then((sketches) =>
|
||||
this.notificationService.notifyRecentSketchesChanged({ sketches })
|
||||
this.notificationService.notifyRecentSketchesDidChange({ sketches })
|
||||
);
|
||||
}
|
||||
|
||||
@ -549,9 +549,8 @@ void loop() {
|
||||
}
|
||||
|
||||
async archive(sketch: Sketch, destinationUri: string): Promise<string> {
|
||||
await this.coreClientProvider.initialized;
|
||||
await this.loadSketch(sketch.uri); // sanity check
|
||||
const { client } = await this.coreClient();
|
||||
const { client } = await this.coreClient;
|
||||
const archivePath = FileUri.fsPath(destinationUri);
|
||||
// The CLI cannot override existing archives, so we have to wipe it manually: https://github.com/arduino/arduino-cli/issues/1160
|
||||
if (await promisify(fs.exists)(archivePath)) {
|
||||
|
@ -67,52 +67,6 @@ describe('arduino-daemon-impl', () => {
|
||||
track.cleanupSync();
|
||||
});
|
||||
|
||||
// it('should parse an error - address already in use error [json]', async function (): Promise<void> {
|
||||
// if (process.platform === 'win32') {
|
||||
// this.skip();
|
||||
// }
|
||||
// let server: net.Server | undefined = undefined;
|
||||
// try {
|
||||
// server = await new Promise<net.Server>(resolve => {
|
||||
// const server = net.createServer();
|
||||
// server.listen(() => resolve(server));
|
||||
// });
|
||||
// const address = server.address() as net.AddressInfo;
|
||||
// await new SilentArduinoDaemonImpl(address.port, 'json').spawnDaemonProcess();
|
||||
// fail('Expected a failure.')
|
||||
// } catch (e) {
|
||||
// expect(e).to.be.instanceOf(DaemonError);
|
||||
// expect(e.code).to.be.equal(DaemonError.ADDRESS_IN_USE);
|
||||
// } finally {
|
||||
// if (server) {
|
||||
// server.close();
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
// it('should parse an error - address already in use error [text]', async function (): Promise<void> {
|
||||
// if (process.platform === 'win32') {
|
||||
// this.skip();
|
||||
// }
|
||||
// let server: net.Server | undefined = undefined;
|
||||
// try {
|
||||
// server = await new Promise<net.Server>(resolve => {
|
||||
// const server = net.createServer();
|
||||
// server.listen(() => resolve(server));
|
||||
// });
|
||||
// const address = server.address() as net.AddressInfo;
|
||||
// await new SilentArduinoDaemonImpl(address.port, 'text').spawnDaemonProcess();
|
||||
// fail('Expected a failure.')
|
||||
// } catch (e) {
|
||||
// expect(e).to.be.instanceOf(DaemonError);
|
||||
// expect(e.code).to.be.equal(DaemonError.ADDRESS_IN_USE);
|
||||
// } finally {
|
||||
// if (server) {
|
||||
// server.close();
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
it('should parse the port address when the log format is json', async () => {
|
||||
const { daemon, port } = await new SilentArduinoDaemonImpl(
|
||||
'json'
|
||||
|
@ -129,6 +129,11 @@
|
||||
"coreContribution": {
|
||||
"copyError": "Copy error messages"
|
||||
},
|
||||
"daemon": {
|
||||
"restart": "Restart Daemon",
|
||||
"start": "Start Daemon",
|
||||
"stop": "Stop Daemon"
|
||||
},
|
||||
"debug": {
|
||||
"debugWithMessage": "Debug - {0}",
|
||||
"debuggingNotSupported": "Debugging is not supported by '{0}'",
|
||||
|
Loading…
x
Reference in New Issue
Block a user