ATL-374: Refactored the Output services.

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
This commit is contained in:
Akos Kitta 2020-09-15 18:00:17 +02:00 committed by Akos Kitta
parent f26dae185b
commit 5f5193932f
53 changed files with 829 additions and 948 deletions

View File

@ -2,13 +2,13 @@
import { VariableContribution, VariableRegistry, Variable } from '@theia/variable-resolver/lib/browser';
import { injectable, inject } from 'inversify';
import { MessageService } from '@theia/core/lib/common/message-service';
import { BoardsServiceClientImpl } from 'arduino-ide-extension/lib/browser/boards/boards-service-client-impl';
import { BoardsServiceProvider } from 'arduino-ide-extension/lib/browser/boards/boards-service-provider';
@injectable()
export class ArduinoVariableResolver implements VariableContribution {
@inject(BoardsServiceClientImpl)
protected readonly boardsServiceClient: BoardsServiceClientImpl;
@inject(BoardsServiceProvider)
protected readonly boardsServiceProvider: BoardsServiceProvider;
@inject(MessageService)
protected readonly messageService: MessageService
@ -27,7 +27,7 @@ export class ArduinoVariableResolver implements VariableContribution {
}
protected async resolveFqbn(): Promise<string | undefined> {
const { boardsConfig } = this.boardsServiceClient;
const { boardsConfig } = this.boardsServiceProvider;
if (!boardsConfig || !boardsConfig.selectedBoard) {
this.messageService.error('No board selected. Please select a board for debugging.');
return undefined;
@ -36,7 +36,7 @@ export class ArduinoVariableResolver implements VariableContribution {
}
protected async resolvePort(): Promise<string | undefined> {
const { boardsConfig } = this.boardsServiceClient;
const { boardsConfig } = this.boardsServiceProvider;
if (!boardsConfig || !boardsConfig.selectedPort) {
return undefined;
}

View File

@ -1,53 +0,0 @@
import { injectable, inject } from 'inversify';
import { ILogger } from '@theia/core/lib/common/logger';
import { Event, Emitter } from '@theia/core/lib/common/event';
import { MessageService } from '@theia/core/lib/common/message-service';
import { ArduinoDaemonClient } from '../common/protocol';
@injectable()
export class ArduinoDaemonClientImpl implements ArduinoDaemonClient {
@inject(ILogger)
protected readonly logger: ILogger;
@inject(MessageService)
protected readonly messageService: MessageService;
protected readonly onStartedEmitter = new Emitter<void>();
protected readonly onStoppedEmitter = new Emitter<void>();
protected _isRunning = false;
notifyStopped(): void {
if (this._isRunning) {
this._isRunning = false;
this.onStoppedEmitter.fire();
this.info('The CLI daemon process has stopped.');
}
}
notifyStarted(): void {
if (!this._isRunning) {
this._isRunning = true;
this.onStartedEmitter.fire();
this.info('The CLI daemon process has started.');
}
}
get onDaemonStarted(): Event<void> {
return this.onStartedEmitter.event;
}
get onDaemonStopped(): Event<void> {
return this.onStoppedEmitter.event;
}
get isRunning(): boolean {
return this._isRunning;
}
protected info(message: string): void {
this.messageService.info(message, { timeout: 3000 });
this.logger.info(message);
}
}

View File

@ -24,7 +24,7 @@ import { TerminalMenus } from '@theia/terminal/lib/browser/terminal-frontend-con
import { inject, injectable, postConstruct } from 'inversify';
import * as React from 'react';
import { MainMenuManager } from '../common/main-menu-manager';
import { BoardsService, BoardsServiceClient, CoreService, Port, SketchesService, ToolOutputServiceClient, ExecutableService } from '../common/protocol';
import { BoardsService, CoreService, Port, SketchesService, ExecutableService } from '../common/protocol';
import { ArduinoDaemon } from '../common/protocol/arduino-daemon';
import { ConfigService } from '../common/protocol/config-service';
import { FileSystemExt } from '../common/protocol/filesystem-ext';
@ -32,7 +32,7 @@ import { ArduinoCommands } from './arduino-commands';
import { BoardsConfig } from './boards/boards-config';
import { BoardsConfigDialog } from './boards/boards-config-dialog';
import { BoardsDataStore } from './boards/boards-data-store';
import { BoardsServiceClientImpl } from './boards/boards-service-client-impl';
import { BoardsServiceProvider } from './boards/boards-service-provider';
import { BoardsToolBarItem } from './boards/boards-toolbar-item';
import { EditorMode } from './editor-mode';
import { ArduinoMenus } from './menu/arduino-menus';
@ -44,6 +44,9 @@ import { HostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted
import { FileService } from '@theia/filesystem/lib/browser/file-service';
const debounce = require('lodash.debounce');
import { OutputService } from '../common/protocol/output-service';
import { NotificationCenter } from './notification-center';
import { Settings } from './contributions/settings';
@injectable()
export class ArduinoFrontendContribution implements FrontendApplicationContribution,
@ -61,15 +64,8 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
@inject(CoreService)
protected readonly coreService: CoreService;
@inject(ToolOutputServiceClient)
protected readonly toolOutputServiceClient: ToolOutputServiceClient;
@inject(BoardsServiceClientImpl)
protected readonly boardsServiceClientImpl: BoardsServiceClientImpl;
// Unused but do not remove it. It's required by DI, otherwise `init` method is not called.
@inject(BoardsServiceClient)
protected readonly boardsServiceClient: BoardsServiceClient;
@inject(BoardsServiceProvider)
protected readonly boardsServiceClientImpl: BoardsServiceProvider;
@inject(SelectionService)
protected readonly selectionService: SelectionService;
@ -151,6 +147,13 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
@inject(ExecutableService)
protected executableService: ExecutableService;
@inject(OutputService)
protected readonly outputService: OutputService;
@inject(NotificationCenter)
protected readonly notificationCenter: NotificationCenter;
protected invalidConfigPopup: Promise<void | 'No' | 'Yes' | undefined> | undefined;
@postConstruct()
protected async init(): Promise<void> {
@ -198,6 +201,21 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
}
}
});
this.notificationCenter.onConfigChanged(({ config }) => {
if (config) {
this.invalidConfigPopup = undefined;
} else {
if (!this.invalidConfigPopup) {
this.invalidConfigPopup = this.messageService.error(`Your CLI configuration is invalid. Do you want to correct it now?`, 'No', 'Yes')
.then(answer => {
if (answer === 'Yes') {
this.commandRegistry.executeCommand(Settings.Commands.OPEN_CLI_CONFIG.id)
}
this.invalidConfigPopup = undefined;
});
}
}
});
}
protected startLanguageServer = debounce((fqbn: string, name: string | undefined) => this.doStartLanguageServer(fqbn, name));

View File

@ -8,17 +8,14 @@ import { WebSocketConnectionProvider } from '@theia/core/lib/browser/messaging/w
import { FrontendApplicationContribution, FrontendApplication as TheiaFrontendApplication } from '@theia/core/lib/browser/frontend-application'
import { LibraryListWidget } from './library/library-list-widget';
import { ArduinoFrontendContribution } from './arduino-frontend-contribution';
import { LibraryServiceServer, LibraryServiceServerPath } from '../common/protocol/library-service';
import { BoardsService, BoardsServicePath, BoardsServiceClient } from '../common/protocol/boards-service';
import { LibraryService, LibraryServicePath } from '../common/protocol/library-service';
import { BoardsService, BoardsServicePath } from '../common/protocol/boards-service';
import { SketchesService, SketchesServicePath } from '../common/protocol/sketches-service';
import { SketchesServiceClientImpl } from '../common/protocol/sketches-service-client-impl';
import { CoreService, CoreServicePath, CoreServiceClient } from '../common/protocol/core-service';
import { CoreService, CoreServicePath } from '../common/protocol/core-service';
import { BoardsListWidget } from './boards/boards-list-widget';
import { BoardsListWidgetFrontendContribution } from './boards/boards-widget-frontend-contribution';
import { ToolOutputServiceClient } from '../common/protocol/tool-output-service';
import { ToolOutputService } from '../common/protocol/tool-output-service';
import { ToolOutputServiceClientImpl } from './tool-output/client-service-impl';
import { BoardsServiceClientImpl } from './boards/boards-service-client-impl';
import { BoardsServiceProvider } from './boards/boards-service-provider';
import { WorkspaceService as TheiaWorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
import { WorkspaceService } from './theia/workspace/workspace-service';
import { OutlineViewContribution as TheiaOutlineViewContribution } from '@theia/outline-view/lib/browser/outline-view-contribution';
@ -50,7 +47,7 @@ import { SearchInWorkspaceFrontendContribution } from './theia/search-in-workspa
import { LibraryListWidgetFrontendContribution } from './library/library-widget-frontend-contribution';
import { MonitorServiceClientImpl } from './monitor/monitor-service-client-impl';
import { MonitorServicePath, MonitorService, MonitorServiceClient } from '../common/protocol/monitor-service';
import { ConfigService, ConfigServicePath, ConfigServiceClient } from '../common/protocol/config-service';
import { ConfigService, ConfigServicePath } from '../common/protocol/config-service';
import { MonitorWidget } from './monitor/monitor-widget';
import { MonitorViewContribution } from './monitor/monitor-view-contribution';
import { MonitorConnection } from './monitor/monitor-connection';
@ -67,8 +64,7 @@ import { EditorMode } from './editor-mode';
import { ListItemRenderer } from './widgets/component-list/list-item-renderer';
import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution';
import { MonacoThemingService } from '@theia/monaco/lib/browser/monaco-theming-service';
import { ArduinoDaemonClientImpl } from './arduino-daemon-client-impl';
import { ArduinoDaemonClient, ArduinoDaemonPath, ArduinoDaemon } from '../common/protocol/arduino-daemon';
import { ArduinoDaemonPath, ArduinoDaemon } from '../common/protocol/arduino-daemon';
import { EditorManager as TheiaEditorManager } from '@theia/editor/lib/browser';
import { EditorManager } from './theia/editor/editor-manager';
import { FrontendConnectionStatusService, ApplicationConnectionStatusContribution } from './theia/core/connection-status-service';
@ -76,8 +72,6 @@ import {
FrontendConnectionStatusService as TheiaFrontendConnectionStatusService,
ApplicationConnectionStatusContribution as TheiaApplicationConnectionStatusContribution
} from '@theia/core/lib/browser/connection-status-service';
import { ConfigServiceClientImpl } from './config-service-client-impl';
import { CoreServiceClientImpl } from './core-service-client-impl';
import { BoardsDataMenuUpdater } from './boards/boards-data-menu-updater';
import { BoardsDataStore } from './boards/boards-data-store';
import { ILogger } from '@theia/core';
@ -116,7 +110,6 @@ import { OutputWidget } from './theia/output/output-widget';
import { BurnBootloader } from './contributions/burn-bootloader';
import { ExamplesServicePath, ExamplesService } from '../common/protocol/examples-service';
import { BuiltInExamples, LibraryExamples } from './contributions/examples';
import { LibraryServiceProvider } from './library/library-service-provider';
import { IncludeLibrary } from './contributions/include-library';
import { OutputChannelManager as TheiaOutputChannelManager } from '@theia/output/lib/common/output-channel';
import { OutputChannelManager } from './theia/output/output-channel';
@ -124,6 +117,10 @@ import { OutputChannelRegistryMainImpl as TheiaOutputChannelRegistryMainImpl, Ou
import { ExecutableService, ExecutableServicePath } from '../common/protocol';
import { MonacoTextModelService as TheiaMonacoTextModelService } from '@theia/monaco/lib/browser/monaco-text-model-service';
import { MonacoTextModelService } from './theia/monaco/monaco-text-model-service';
import { OutputServiceImpl } from './output-service-impl';
import { OutputServicePath, OutputService } from '../common/protocol/output-service';
import { NotificationCenter } from './notification-center';
import { NotificationServicePath, NotificationServiceServer } from '../common/protocol';
const ElementQueries = require('css-element-queries/src/ElementQueries');
@ -153,8 +150,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(ListItemRenderer).toSelf().inSingletonScope();
// Library service
bind(LibraryServiceProvider).toSelf().inSingletonScope();
bind(LibraryServiceServer).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, LibraryServiceServerPath)).inSingletonScope();
bind(LibraryService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, LibraryServicePath)).inSingletonScope();
// Library list widget
bind(LibraryListWidget).toSelf();
@ -170,34 +166,13 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(SketchesServiceClientImpl).toSelf().inSingletonScope();
// Config service
bind(ConfigService).toDynamicValue(context => {
const connection = context.container.get(WebSocketConnectionProvider);
const client = context.container.get(ConfigServiceClientImpl);
return connection.createProxy(ConfigServicePath, client);
}).inSingletonScope();
bind(ConfigServiceClientImpl).toSelf().inSingletonScope();
bind(ConfigServiceClient).toDynamicValue(context => {
const client = context.container.get(ConfigServiceClientImpl);
WebSocketConnectionProvider.createProxy(context.container, ConfigServicePath, client);
return client;
}).inSingletonScope();
bind(ConfigService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, ConfigServicePath)).inSingletonScope();
// Boards service
bind(BoardsService).toDynamicValue(context => {
const connection = context.container.get(WebSocketConnectionProvider);
const client = context.container.get(BoardsServiceClientImpl);
return connection.createProxy(BoardsServicePath, client);
}).inSingletonScope();
bind(BoardsService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, BoardsServicePath)).inSingletonScope();
// Boards service client to receive and delegate notifications from the backend.
bind(BoardsServiceClientImpl).toSelf().inSingletonScope();
bind(FrontendApplicationContribution).toService(BoardsServiceClientImpl);
bind(BoardsServiceClient).toDynamicValue(async context => {
const client = context.container.get(BoardsServiceClientImpl);
const service = context.container.get<BoardsService>(BoardsService);
await client.init(service);
WebSocketConnectionProvider.createProxy(context.container, BoardsServicePath, client);
return client;
}).inSingletonScope();
bind(BoardsServiceProvider).toSelf().inSingletonScope();
bind(FrontendApplicationContribution).toService(BoardsServiceProvider);
// To be able to track, and update the menu based on the core settings (aka. board details) of the currently selected board.
bind(FrontendApplicationContribution).to(BoardsDataMenuUpdater).inSingletonScope();
@ -230,26 +205,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
})
// Core service
bind(CoreService).toDynamicValue(context => {
const connection = context.container.get(WebSocketConnectionProvider);
const client = context.container.get(CoreServiceClientImpl);
return connection.createProxy(CoreServicePath, client);
}).inSingletonScope();
// Core service client to receive and delegate notifications when the index or the library index has been updated.
bind(CoreServiceClientImpl).toSelf().inSingletonScope();
bind(CoreServiceClient).toDynamicValue(context => {
const client = context.container.get(CoreServiceClientImpl);
WebSocketConnectionProvider.createProxy(context.container, CoreServicePath, client);
return client;
}).inSingletonScope();
// Tool output service client
bind(ToolOutputServiceClientImpl).toSelf().inSingletonScope();
bind(ToolOutputServiceClient).toDynamicValue(context => {
const client = context.container.get(ToolOutputServiceClientImpl);
WebSocketConnectionProvider.createProxy(context.container, ToolOutputService.SERVICE_PATH, client);
return client;
}).inSingletonScope();
bind(CoreService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, CoreServicePath)).inSingletonScope();
// Serial monitor
bind(MonitorModel).toSelf().inSingletonScope();
@ -341,18 +297,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(ShellLayoutRestorer).toSelf().inSingletonScope();
rebind(TheiaShellLayoutRestorer).toService(ShellLayoutRestorer);
// Arduino daemon client. Receives notifications from the backend if the CLI daemon process has been restarted.
bind(ArduinoDaemon).toDynamicValue(context => {
const connection = context.container.get(WebSocketConnectionProvider);
const client = context.container.get(ArduinoDaemonClientImpl);
return connection.createProxy(ArduinoDaemonPath, client);
}).inSingletonScope();
bind(ArduinoDaemonClientImpl).toSelf().inSingletonScope();
bind(ArduinoDaemonClient).toDynamicValue(context => {
const client = context.container.get(ArduinoDaemonClientImpl);
WebSocketConnectionProvider.createProxy(context.container, ArduinoDaemonPath, client);
return client;
}).inSingletonScope();
bind(ArduinoDaemon).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, ArduinoDaemonPath)).inSingletonScope();
// File-system extension
bind(FileSystemExt).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, FileSystemExtPath)).inSingletonScope();
@ -379,4 +324,13 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
Contribution.configure(bind, BuiltInExamples);
Contribution.configure(bind, LibraryExamples);
Contribution.configure(bind, IncludeLibrary);
bind(OutputServiceImpl).toSelf().inSingletonScope().onActivation(({ container }, outputService) => {
WebSocketConnectionProvider.createProxy(container, OutputServicePath, outputService);
return outputService;
});
bind(OutputService).toService(OutputServiceImpl);
bind(NotificationCenter).toSelf().inSingletonScope();
bind(NotificationServiceServer).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, NotificationServicePath)).inSingletonScope();
});

View File

@ -1,8 +1,8 @@
import { injectable, inject } from 'inversify';
import { MessageService } from '@theia/core/lib/common/message-service';
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
import { BoardsService, Board } from '../../common/protocol/boards-service';
import { BoardsServiceClientImpl } from './boards-service-client-impl';
import { BoardsService, BoardsPackage } from '../../common/protocol/boards-service';
import { BoardsServiceProvider } from './boards-service-provider';
import { BoardsListWidgetFrontendContribution } from './boards-widget-frontend-contribution';
import { InstallationProgressDialog } from '../widgets/progress-dialog';
import { BoardsConfig } from './boards-config';
@ -20,8 +20,8 @@ export class BoardsAutoInstaller implements FrontendApplicationContribution {
@inject(BoardsService)
protected readonly boardsService: BoardsService;
@inject(BoardsServiceClientImpl)
protected readonly boardsServiceClient: BoardsServiceClientImpl;
@inject(BoardsServiceProvider)
protected readonly boardsServiceClient: BoardsServiceProvider;
@inject(BoardsListWidgetFrontendContribution)
protected readonly boardsManagerFrontendContribution: BoardsListWidgetFrontendContribution;
@ -36,7 +36,7 @@ export class BoardsAutoInstaller implements FrontendApplicationContribution {
if (selectedBoard) {
this.boardsService.search({}).then(packages => {
const candidates = packages
.filter(pkg => pkg.boards.some(board => Board.sameAs(board, selectedBoard)))
.filter(pkg => BoardsPackage.contains(selectedBoard, pkg))
.filter(({ installable, installedVersion }) => installable && !installedVersion);
for (const candidate of candidates) {
// tslint:disable-next-line:max-line-length

View File

@ -4,9 +4,8 @@ import { Emitter } from '@theia/core/lib/common/event';
import { ReactWidget, Message } from '@theia/core/lib/browser';
import { BoardsService } from '../../common/protocol/boards-service';
import { BoardsConfig } from './boards-config';
import { BoardsServiceClientImpl } from './boards-service-client-impl';
import { CoreServiceClientImpl } from '../core-service-client-impl';
import { ArduinoDaemonClientImpl } from '../arduino-daemon-client-impl';
import { BoardsServiceProvider } from './boards-service-provider';
import { NotificationCenter } from '../notification-center';
@injectable()
export class BoardsConfigDialogWidget extends ReactWidget {
@ -14,14 +13,11 @@ export class BoardsConfigDialogWidget extends ReactWidget {
@inject(BoardsService)
protected readonly boardsService: BoardsService;
@inject(BoardsServiceClientImpl)
protected readonly boardsServiceClient: BoardsServiceClientImpl;
@inject(BoardsServiceProvider)
protected readonly boardsServiceClient: BoardsServiceProvider;
@inject(CoreServiceClientImpl)
protected readonly coreServiceClient: CoreServiceClientImpl;
@inject(ArduinoDaemonClientImpl)
protected readonly daemonClient: ArduinoDaemonClientImpl;
@inject(NotificationCenter)
protected readonly notificationCenter: NotificationCenter;
protected readonly onBoardConfigChangedEmitter = new Emitter<BoardsConfig.Config>();
readonly onBoardConfigChanged = this.onBoardConfigChangedEmitter.event;
@ -44,10 +40,8 @@ export class BoardsConfigDialogWidget extends ReactWidget {
protected render(): React.ReactNode {
return <div className='selectBoardContainer'>
<BoardsConfig
boardsService={this.boardsService}
boardsServiceClient={this.boardsServiceClient}
coreServiceClient={this.coreServiceClient}
daemonClient={this.daemonClient}
boardsServiceProvider={this.boardsServiceClient}
notificationCenter={this.notificationCenter}
onConfigChange={this.fireConfigChanged}
onFocusNodeSet={this.setFocusNode} />
</div>;

View File

@ -4,7 +4,7 @@ import { AbstractDialog, DialogProps, Widget, DialogError } from '@theia/core/li
import { BoardsService } from '../../common/protocol/boards-service';
import { BoardsConfig } from './boards-config';
import { BoardsConfigDialogWidget } from './boards-config-dialog-widget';
import { BoardsServiceClientImpl } from './boards-service-client-impl';
import { BoardsServiceProvider } from './boards-service-provider';
@injectable()
export class BoardsConfigDialogProps extends DialogProps {
@ -19,8 +19,8 @@ export class BoardsConfigDialog extends AbstractDialog<BoardsConfig.Config> {
@inject(BoardsService)
protected readonly boardService: BoardsService;
@inject(BoardsServiceClientImpl)
protected readonly boardsServiceClient: BoardsServiceClientImpl;
@inject(BoardsServiceProvider)
protected readonly boardsServiceClient: BoardsServiceProvider;
protected config: BoardsConfig.Config = {};

View File

@ -1,9 +1,10 @@
import * as React from 'react';
import { DisposableCollection } from '@theia/core';
import { BoardsService, Board, Port, AttachedBoardsChangeEvent } from '../../common/protocol/boards-service';
import { BoardsServiceClientImpl } from './boards-service-client-impl';
import { CoreServiceClientImpl } from '../core-service-client-impl';
import { ArduinoDaemonClientImpl } from '../arduino-daemon-client-impl';
import { notEmpty } from '@theia/core/lib/common/objects';
import { DisposableCollection } from '@theia/core/lib/common/disposable';
import { Board, Port, AttachedBoardsChangeEvent } from '../../common/protocol/boards-service';
import { BoardsServiceProvider } from './boards-service-provider';
import { NotificationCenter } from '../notification-center';
import { MaybePromise } from '@theia/core';
export namespace BoardsConfig {
@ -13,10 +14,8 @@ export namespace BoardsConfig {
}
export interface Props {
readonly boardsService: BoardsService;
readonly boardsServiceClient: BoardsServiceClientImpl;
readonly coreServiceClient: CoreServiceClientImpl;
readonly daemonClient: ArduinoDaemonClientImpl;
readonly boardsServiceProvider: BoardsServiceProvider;
readonly notificationCenter: NotificationCenter;
readonly onConfigChange: (config: Config) => void;
readonly onFocusNodeSet: (element: HTMLElement | undefined) => void;
}
@ -70,7 +69,7 @@ export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConf
constructor(props: BoardsConfig.Props) {
super(props);
const { boardsConfig } = props.boardsServiceClient;
const { boardsConfig } = props.boardsServiceProvider;
this.state = {
searchResults: [],
knownPorts: [],
@ -82,18 +81,17 @@ export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConf
componentDidMount() {
this.updateBoards();
this.props.boardsService.getAvailablePorts().then(ports => this.updatePorts(ports));
const { boardsServiceClient, coreServiceClient, daemonClient } = this.props;
this.updatePorts(this.props.boardsServiceProvider.availableBoards.map(({ port }) => port).filter(notEmpty));
this.toDispose.pushAll([
boardsServiceClient.onAttachedBoardsChanged(event => this.updatePorts(event.newState.ports, AttachedBoardsChangeEvent.diff(event).detached.ports)),
boardsServiceClient.onBoardsConfigChanged(({ selectedBoard, selectedPort }) => {
this.props.notificationCenter.onAttachedBoardsChanged(event => this.updatePorts(event.newState.ports, AttachedBoardsChangeEvent.diff(event).detached.ports)),
this.props.boardsServiceProvider.onBoardsConfigChanged(({ selectedBoard, selectedPort }) => {
this.setState({ selectedBoard, selectedPort }, () => this.fireConfigChanged());
}),
boardsServiceClient.onBoardsPackageInstalled(() => this.updateBoards(this.state.query)),
boardsServiceClient.onBoardsPackageUninstalled(() => this.updateBoards(this.state.query)),
coreServiceClient.onIndexUpdated(() => this.updateBoards(this.state.query)),
daemonClient.onDaemonStarted(() => this.updateBoards(this.state.query)),
daemonClient.onDaemonStopped(() => this.setState({ searchResults: [] }))
this.props.notificationCenter.onPlatformInstalled(() => this.updateBoards(this.state.query)),
this.props.notificationCenter.onPlatformUninstalled(() => this.updateBoards(this.state.query)),
this.props.notificationCenter.onIndexUpdated(() => this.updateBoards(this.state.query)),
this.props.notificationCenter.onDaemonStarted(() => this.updateBoards(this.state.query)),
this.props.notificationCenter.onDaemonStopped(() => this.setState({ searchResults: [] }))
]);
}
@ -127,14 +125,14 @@ export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConf
}
protected queryBoards = (options: { query?: string } = {}): Promise<Array<Board & { packageName: string }>> => {
return this.props.boardsServiceClient.searchBoards(options);
return this.props.boardsServiceProvider.searchBoards(options);
}
protected get availablePorts(): Promise<Port[]> {
return this.props.boardsService.getAvailablePorts();
protected get availablePorts(): MaybePromise<Port[]> {
return this.props.boardsServiceProvider.availableBoards.map(({ port }) => port).filter(notEmpty);
}
protected queryPorts = async (availablePorts: Promise<Port[]> = this.availablePorts) => {
protected queryPorts = async (availablePorts: MaybePromise<Port[]> = this.availablePorts) => {
const ports = await availablePorts;
return { knownPorts: ports.sort(Port.compare) };
}

View File

@ -3,7 +3,7 @@ import { inject, injectable } from 'inversify';
import { CommandRegistry } from '@theia/core/lib/common/command';
import { MenuModelRegistry, MenuNode } from '@theia/core/lib/common/menu';
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
import { BoardsServiceClientImpl } from './boards-service-client-impl';
import { BoardsServiceProvider } from './boards-service-provider';
import { Board, ConfigOption, Programmer } from '../../common/protocol';
import { FrontendApplicationContribution } from '@theia/core/lib/browser';
import { BoardsDataStore } from './boards-data-store';
@ -25,8 +25,8 @@ export class BoardsDataMenuUpdater implements FrontendApplicationContribution {
@inject(BoardsDataStore)
protected readonly boardsDataStore: BoardsDataStore;
@inject(BoardsServiceClientImpl)
protected readonly boardsServiceClient: BoardsServiceClientImpl;
@inject(BoardsServiceProvider)
protected readonly boardsServiceClient: BoardsServiceProvider;
protected readonly queue = new PQueue({ autoStart: true, concurrency: 1 });
protected readonly toDisposeOnBoardChange = new DisposableCollection();

View File

@ -5,8 +5,8 @@ import { MaybePromise } from '@theia/core/lib/common/types';
import { Event, Emitter } from '@theia/core/lib/common/event';
import { FrontendApplicationContribution, LocalStorageService } from '@theia/core/lib/browser';
import { notEmpty } from '../../common/utils';
import { BoardsServiceClientImpl } from './boards-service-client-impl';
import { BoardsService, ConfigOption, Installable, BoardDetails, Programmer } from '../../common/protocol';
import { NotificationCenter } from '../notification-center';
@injectable()
export class BoardsDataStore implements FrontendApplicationContribution {
@ -18,8 +18,8 @@ export class BoardsDataStore implements FrontendApplicationContribution {
@inject(BoardsService)
protected readonly boardsService: BoardsService;
@inject(BoardsServiceClientImpl)
protected readonly boardsServiceClient: BoardsServiceClientImpl;
@inject(NotificationCenter)
protected readonly notificationCenter: NotificationCenter;
@inject(LocalStorageService)
protected readonly storageService: LocalStorageService;
@ -27,7 +27,7 @@ export class BoardsDataStore implements FrontendApplicationContribution {
protected readonly onChangedEmitter = new Emitter<void>();
onStart(): void {
this.boardsServiceClient.onBoardsPackageInstalled(async ({ item }) => {
this.notificationCenter.onPlatformInstalled(async ({ item }) => {
const { installedVersion: version } = item;
if (!version) {
return;
@ -76,7 +76,8 @@ export class BoardsDataStore implements FrontendApplicationContribution {
const key = this.getStorageKey(fqbn, version);
let data = await this.storageService.getData<BoardsDataStore.Data | undefined>(key, undefined);
if (data) {
if (data.programmers !== undefined) { // to be backward compatible. We did not save the `programmers` into the `localStorage`.
// If `configOptions` is empty we rather reload the data. See arduino/arduino-cli#954 and arduino/arduino-cli#955.
if (data.configOptions.length && data.programmers !== undefined) { // to be backward compatible. We did not save the `programmers` into the `localStorage`.
return data;
}
}

View File

@ -1,4 +1,4 @@
import { inject, injectable } from 'inversify';
import { inject, injectable, postConstruct } from 'inversify';
import { BoardsPackage, BoardsService } from '../../common/protocol/boards-service';
import { ListWidget } from '../widgets/component-list/list-widget';
import { ListItemRenderer } from '../widgets/component-list/list-item-renderer';
@ -24,4 +24,13 @@ export class BoardsListWidget extends ListWidget<BoardsPackage> {
});
}
@postConstruct()
protected init(): void {
super.init();
this.toDispose.pushAll([
this.notificationCenter.onPlatformInstalled(() => this.refresh(undefined)),
this.notificationCenter.onPlatformUninstalled(() => this.refresh(undefined)),
]);
}
}

View File

@ -10,14 +10,12 @@ import {
Board,
BoardsService,
BoardsPackage,
InstalledEvent,
UninstalledEvent,
BoardsServiceClient,
AttachedBoardsChangeEvent
} from '../../common/protocol';
import { BoardsConfig } from './boards-config';
import { naturalCompare } from '../../common/utils';
import { compareAnything } from '../theia/monaco/comparers';
import { NotificationCenter } from '../notification-center';
interface BoardMatch {
readonly board: Board & Readonly<{ packageName: string }>;
@ -25,7 +23,7 @@ interface BoardMatch {
}
@injectable()
export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApplicationContribution {
export class BoardsServiceProvider implements FrontendApplicationContribution {
@inject(ILogger)
protected logger: ILogger;
@ -36,9 +34,12 @@ export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApp
@inject(StorageService)
protected storageService: StorageService;
protected readonly onBoardsPackageInstalledEmitter = new Emitter<InstalledEvent<BoardsPackage>>();
protected readonly onBoardsPackageUninstalledEmitter = new Emitter<UninstalledEvent<BoardsPackage>>();
protected readonly onAttachedBoardsChangedEmitter = new Emitter<AttachedBoardsChangeEvent>();
@inject(BoardsService)
protected boardsService: BoardsService;
@inject(NotificationCenter)
protected notificationCenter: NotificationCenter;
protected readonly onBoardsConfigChangedEmitter = new Emitter<BoardsConfig.Config>();
protected readonly onAvailableBoardsChangedEmitter = new Emitter<AvailableBoard[]>();
@ -54,14 +55,7 @@ export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApp
protected _attachedBoards: Board[] = []; // This does not contain the `Unknown` boards. They're visible from the available ports only.
protected _availablePorts: Port[] = [];
protected _availableBoards: AvailableBoard[] = [];
protected boardsService: BoardsService;
/**
* Event when the state of the attached/detached boards has changed. For instance, the user have detached a physical board.
*/
readonly onAttachedBoardsChanged = this.onAttachedBoardsChangedEmitter.event;
readonly onBoardsPackageInstalled = this.onBoardsPackageInstalledEmitter.event;
readonly onBoardsPackageUninstalled = this.onBoardsPackageUninstalledEmitter.event;
/**
* Unlike `onAttachedBoardsChanged` this even fires when the user modifies the selected board in the IDE.\
* This even also fires, when the boards package was not available for the currently selected board,
@ -72,33 +66,72 @@ export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApp
readonly onBoardsConfigChanged = this.onBoardsConfigChangedEmitter.event;
readonly onAvailableBoardsChanged = this.onAvailableBoardsChangedEmitter.event;
async onStart(): Promise<void> {
return this.loadState();
}
onStart(): void {
this.notificationCenter.onAttachedBoardsChanged(this.notifyAttachedBoardsChanged.bind(this));
this.notificationCenter.onPlatformInstalled(this.notifyPlatformInstalled.bind(this));
this.notificationCenter.onPlatformUninstalled(this.notifyPlatformUninstalled.bind(this));
/**
* When the FE connects to the BE, the BE stets the known boards and ports.\
* This is a DI workaround for not being able to inject the service into the client.
*/
async init(boardsService: BoardsService): Promise<void> {
this.boardsService = boardsService;
const [attachedBoards, availablePorts] = await Promise.all([
Promise.all([
this.boardsService.getAttachedBoards(),
this.boardsService.getAvailablePorts()
]);
this._attachedBoards = attachedBoards;
this._availablePorts = availablePorts;
this.reconcileAvailableBoards().then(() => this.tryReconnect());
this.boardsService.getAvailablePorts(),
this.loadState()
]).then(([attachedBoards, availablePorts]) => {
this._attachedBoards = attachedBoards;
this._availablePorts = availablePorts;
this.reconcileAvailableBoards().then(() => this.tryReconnect());
});
}
notifyAttachedBoardsChanged(event: AttachedBoardsChangeEvent): void {
this.logger.info('Attached boards and available ports changed: ', JSON.stringify(event));
protected notifyAttachedBoardsChanged(event: AttachedBoardsChangeEvent): void {
if (!AttachedBoardsChangeEvent.isEmpty(event)) {
this.logger.info('Attached boards and available ports changed:');
this.logger.info(AttachedBoardsChangeEvent.toString(event));
this.logger.info(`------------------------------------------`);
}
this._attachedBoards = event.newState.boards;
this.onAttachedBoardsChangedEmitter.fire(event);
this._availablePorts = event.newState.ports;
this.reconcileAvailableBoards().then(() => this.tryReconnect());
}
protected notifyPlatformInstalled(event: { item: BoardsPackage }): void {
this.logger.info('Boards package installed: ', JSON.stringify(event));
const { selectedBoard } = this.boardsConfig;
const { installedVersion, id } = event.item;
if (selectedBoard) {
const installedBoard = event.item.boards.find(({ name }) => name === selectedBoard.name);
if (installedBoard && (!selectedBoard.fqbn || selectedBoard.fqbn === installedBoard.fqbn)) {
this.logger.info(`Board package ${id}[${installedVersion}] was installed. Updating the FQBN of the currently selected ${selectedBoard.name} board. [FQBN: ${installedBoard.fqbn}]`);
this.boardsConfig = {
...this.boardsConfig,
selectedBoard: installedBoard
};
return;
}
// Trigger a board re-set. See: https://github.com/arduino/arduino-cli/issues/954
// E.g: install `adafruit:avr`, then select `adafruit:avr:adafruit32u4` board, and finally install the required `arduino:avr`
this.boardsConfig = this.boardsConfig;
}
}
protected notifyPlatformUninstalled(event: { item: BoardsPackage }): void {
this.logger.info('Boards package uninstalled: ', JSON.stringify(event));
const { selectedBoard } = this.boardsConfig;
if (selectedBoard && selectedBoard.fqbn) {
const uninstalledBoard = event.item.boards.find(({ name }) => name === selectedBoard.name);
if (uninstalledBoard && uninstalledBoard.fqbn === selectedBoard.fqbn) {
this.logger.info(`Board package ${event.item.id} was uninstalled. Discarding the FQBN of the currently selected ${selectedBoard.name} board.`);
const selectedBoardWithoutFqbn = {
name: selectedBoard.name
// No FQBN
};
this.boardsConfig = {
...this.boardsConfig,
selectedBoard: selectedBoardWithoutFqbn
};
}
}
}
protected async tryReconnect(): Promise<boolean> {
if (this.latestValidBoardsConfig && !this.canUploadTo(this.boardsConfig)) {
for (const board of this.availableBoards.filter(({ state }) => state !== AvailableBoard.State.incomplete)) {
@ -127,43 +160,6 @@ export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApp
return false;
}
notifyInstalled(event: InstalledEvent<BoardsPackage>): void {
this.logger.info('Boards package installed: ', JSON.stringify(event));
this.onBoardsPackageInstalledEmitter.fire(event);
const { selectedBoard } = this.boardsConfig;
const { installedVersion, id } = event.item;
if (selectedBoard) {
const installedBoard = event.item.boards.find(({ name }) => name === selectedBoard.name);
if (installedBoard && (!selectedBoard.fqbn || selectedBoard.fqbn === installedBoard.fqbn)) {
this.logger.info(`Board package ${id}[${installedVersion}] was installed. Updating the FQBN of the currently selected ${selectedBoard.name} board. [FQBN: ${installedBoard.fqbn}]`);
this.boardsConfig = {
...this.boardsConfig,
selectedBoard: installedBoard
};
}
}
}
notifyUninstalled(event: UninstalledEvent<BoardsPackage>): void {
this.logger.info('Boards package uninstalled: ', JSON.stringify(event));
this.onBoardsPackageUninstalledEmitter.fire(event);
const { selectedBoard } = this.boardsConfig;
if (selectedBoard && selectedBoard.fqbn) {
const uninstalledBoard = event.item.boards.find(({ name }) => name === selectedBoard.name);
if (uninstalledBoard && uninstalledBoard.fqbn === selectedBoard.fqbn) {
this.logger.info(`Board package ${event.item.id} was uninstalled. Discarding the FQBN of the currently selected ${selectedBoard.name} board.`);
const selectedBoardWithoutFqbn = {
name: selectedBoard.name
// No FQBN
};
this.boardsConfig = {
...this.boardsConfig,
selectedBoard: selectedBoardWithoutFqbn
};
}
}
}
set boardsConfig(config: BoardsConfig.Config) {
this.doSetBoardsConfig(config);
this.saveState().finally(() => this.reconcileAvailableBoards().finally(() => this.onBoardsConfigChangedEmitter.fire(this._boardsConfig)));

View File

@ -5,7 +5,7 @@ import { DisposableCollection } from '@theia/core/lib/common/disposable';
import { Port } from '../../common/protocol';
import { BoardsConfig } from './boards-config';
import { ArduinoCommands } from '../arduino-commands';
import { BoardsServiceClientImpl, AvailableBoard } from './boards-service-client-impl';
import { BoardsServiceProvider, AvailableBoard } from './boards-service-provider';
export interface BoardsDropDownListCoords {
readonly top: number;
@ -181,7 +181,7 @@ export class BoardsToolBarItem extends React.Component<BoardsToolBarItem.Props,
export namespace BoardsToolBarItem {
export interface Props {
readonly boardsServiceClient: BoardsServiceClientImpl;
readonly boardsServiceClient: BoardsServiceProvider;
readonly commands: CommandRegistry;
}

View File

@ -16,9 +16,9 @@ import {
} from '@theia/core/lib/browser/quick-open';
import { naturalCompare } from '../../../common/utils';
import { BoardsService, Port, Board, ConfigOption, ConfigValue } from '../../../common/protocol';
import { CoreServiceClientImpl } from '../../core-service-client-impl';
import { BoardsDataStore } from '../boards-data-store';
import { BoardsServiceClientImpl, AvailableBoard } from '../boards-service-client-impl';
import { BoardsServiceProvider, AvailableBoard } from '../boards-service-provider';
import { NotificationCenter } from '../../notification-center';
@injectable()
export class BoardsQuickOpenService implements QuickOpenContribution, QuickOpenModel, QuickOpenHandler, CommandContribution, KeybindingContribution, Command {
@ -38,14 +38,14 @@ export class BoardsQuickOpenService implements QuickOpenContribution, QuickOpenM
@inject(BoardsService)
protected readonly boardsService: BoardsService;
@inject(BoardsServiceClientImpl)
protected readonly boardsServiceClient: BoardsServiceClientImpl;
@inject(BoardsServiceProvider)
protected readonly boardsServiceClient: BoardsServiceProvider;
@inject(BoardsDataStore)
protected readonly boardsDataStore: BoardsDataStore;
@inject(CoreServiceClientImpl)
protected coreServiceClient: CoreServiceClientImpl;
@inject(NotificationCenter)
protected notificationCenter: NotificationCenter;
protected isOpen: boolean = false;
protected currentQuery: string = '';
@ -59,7 +59,7 @@ export class BoardsQuickOpenService implements QuickOpenContribution, QuickOpenM
// `init` name is used by the `QuickOpenHandler`.
@postConstruct()
protected postConstruct(): void {
this.coreServiceClient.onIndexUpdated(() => this.update(this.availableBoards));
this.notificationCenter.onIndexUpdated(() => this.update(this.availableBoards));
this.boardsServiceClient.onAvailableBoardsChanged(availableBoards => this.update(availableBoards));
this.update(this.boardsServiceClient.availableBoards);
}

View File

@ -1,52 +0,0 @@
import { injectable, inject } from 'inversify';
import { ILogger } from '@theia/core/lib/common/logger';
import { Event, Emitter } from '@theia/core/lib/common/event';
import { CommandService } from '@theia/core/lib/common/command';
import { MessageService } from '@theia/core/lib/common/message-service';
import { ConfigServiceClient, Config } from '../common/protocol';
import { Settings } from './contributions/settings';
@injectable()
export class ConfigServiceClientImpl implements ConfigServiceClient {
@inject(CommandService)
protected readonly commandService: CommandService;
@inject(ILogger)
protected readonly logger: ILogger;
@inject(MessageService)
protected readonly messageService: MessageService;
protected readonly onConfigChangedEmitter = new Emitter<Config>();
protected invalidConfigPopup: Promise<void | 'No' | 'Yes' | undefined> | undefined;
notifyConfigChanged(config: Config): void {
this.invalidConfigPopup = undefined;
this.info(`The CLI configuration has been successfully reloaded.`);
this.onConfigChangedEmitter.fire(config);
}
notifyInvalidConfig(): void {
if (!this.invalidConfigPopup) {
this.invalidConfigPopup = this.messageService.error(`Your CLI configuration is invalid. Do you want to correct it now?`, 'No', 'Yes')
.then(answer => {
if (answer === 'Yes') {
this.commandService.executeCommand(Settings.Commands.OPEN_CLI_CONFIG.id)
}
this.invalidConfigPopup = undefined;
})
}
}
get onConfigChanged(): Event<Config> {
return this.onConfigChangedEmitter.event;
}
protected info(message: string): void {
this.messageService.info(message, { timeout: 3000 });
this.logger.info(message);
}
}

View File

@ -4,7 +4,7 @@ import { CoreService } from '../../common/protocol';
import { ArduinoMenus } from '../menu/arduino-menus';
import { BoardsDataStore } from '../boards/boards-data-store';
import { MonitorConnection } from '../monitor/monitor-connection';
import { BoardsServiceClientImpl } from '../boards/boards-service-client-impl';
import { BoardsServiceProvider } from '../boards/boards-service-provider';
import { SketchContribution, Command, CommandRegistry, MenuModelRegistry } from './contribution';
@injectable()
@ -19,8 +19,8 @@ export class BurnBootloader extends SketchContribution {
@inject(BoardsDataStore)
protected readonly boardsDataStore: BoardsDataStore;
@inject(BoardsServiceClientImpl)
protected readonly boardsServiceClientImpl: BoardsServiceClientImpl;
@inject(BoardsServiceProvider)
protected readonly boardsServiceClientImpl: BoardsServiceProvider;
@inject(OutputChannelManager)
protected readonly outputChannelManager: OutputChannelManager;

View File

@ -5,10 +5,10 @@ import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposa
import { OpenSketch } from './open-sketch';
import { ArduinoMenus } from '../menu/arduino-menus';
import { MainMenuManager } from '../../common/main-menu-manager';
import { LibraryServiceProvider } from '../library/library-service-provider';
import { BoardsServiceClientImpl } from '../boards/boards-service-client-impl';
import { BoardsServiceProvider } from '../boards/boards-service-provider';
import { ExamplesService, ExampleContainer } from '../../common/protocol/examples-service';
import { SketchContribution, CommandRegistry, MenuModelRegistry } from './contribution';
import { NotificationCenter } from '../notification-center';
@injectable()
export abstract class Examples extends SketchContribution {
@ -25,8 +25,8 @@ export abstract class Examples extends SketchContribution {
@inject(ExamplesService)
protected readonly examplesService: ExamplesService;
@inject(BoardsServiceClientImpl)
protected readonly boardsServiceClient: BoardsServiceClientImpl;
@inject(BoardsServiceProvider)
protected readonly boardsServiceClient: BoardsServiceProvider;
protected readonly toDispose = new DisposableCollection();
@ -113,15 +113,15 @@ export class BuiltInExamples extends Examples {
@injectable()
export class LibraryExamples extends Examples {
@inject(LibraryServiceProvider)
protected readonly libraryServiceProvider: LibraryServiceProvider;
@inject(NotificationCenter)
protected readonly notificationCenter: NotificationCenter;
protected readonly queue = new PQueue({ autoStart: true, concurrency: 1 });
onStart(): void {
this.register(); // no `await`
this.libraryServiceProvider.onLibraryPackageInstalled(() => this.register());
this.libraryServiceProvider.onLibraryPackageUninstalled(() => this.register());
this.notificationCenter.onLibraryInstalled(() => this.register());
this.notificationCenter.onLibraryUninstalled(() => this.register());
}
protected handleBoardChanged(fqbn: string | undefined): void {

View File

@ -6,12 +6,12 @@ import { EditorManager } from '@theia/editor/lib/browser';
import { MenuModelRegistry, MenuPath } from '@theia/core/lib/common/menu';
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
import { ArduinoMenus } from '../menu/arduino-menus';
import { LibraryPackage, LibraryLocation } from '../../common/protocol';
import { LibraryPackage, LibraryLocation, LibraryService } from '../../common/protocol';
import { MainMenuManager } from '../../common/main-menu-manager';
import { LibraryListWidget } from '../library/library-list-widget';
import { LibraryServiceProvider } from '../library/library-service-provider';
import { BoardsServiceClientImpl } from '../boards/boards-service-client-impl';
import { BoardsServiceProvider } from '../boards/boards-service-provider';
import { SketchContribution, Command, CommandRegistry } from './contribution';
import { NotificationCenter } from '../notification-center';
@injectable()
export class IncludeLibrary extends SketchContribution {
@ -28,11 +28,14 @@ export class IncludeLibrary extends SketchContribution {
@inject(EditorManager)
protected readonly editorManager: EditorManager;
@inject(LibraryServiceProvider)
protected readonly libraryServiceProvider: LibraryServiceProvider;
@inject(NotificationCenter)
protected readonly notificationCenter: NotificationCenter;
@inject(BoardsServiceClientImpl)
protected readonly boardsServiceClient: BoardsServiceClientImpl;
@inject(BoardsServiceProvider)
protected readonly boardsServiceClient: BoardsServiceProvider;
@inject(LibraryService)
protected readonly libraryService: LibraryService;
protected readonly queue = new PQueue({ autoStart: true, concurrency: 1 });
protected readonly toDispose = new DisposableCollection();
@ -40,8 +43,8 @@ export class IncludeLibrary extends SketchContribution {
onStart(): void {
this.updateMenuActions();
this.boardsServiceClient.onBoardsConfigChanged(() => this.updateMenuActions())
this.libraryServiceProvider.onLibraryPackageInstalled(() => this.updateMenuActions());
this.libraryServiceProvider.onLibraryPackageUninstalled(() => this.updateMenuActions());
this.notificationCenter.onLibraryInstalled(() => this.updateMenuActions());
this.notificationCenter.onLibraryUninstalled(() => this.updateMenuActions());
}
registerCommands(registry: CommandRegistry): void {
@ -62,7 +65,7 @@ export class IncludeLibrary extends SketchContribution {
const fqbn = this.boardsServiceClient.boardsConfig.selectedBoard?.fqbn;
// Do not show board specific examples, when no board is selected.
if (fqbn) {
libraries.push(...await this.libraryServiceProvider.list({ fqbn }));
libraries.push(...await this.libraryService.list({ fqbn }));
}
// `Include Library` submenu

View File

@ -5,7 +5,7 @@ import { ArduinoMenus } from '../menu/arduino-menus';
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
import { BoardsDataStore } from '../boards/boards-data-store';
import { MonitorConnection } from '../monitor/monitor-connection';
import { BoardsServiceClientImpl } from '../boards/boards-service-client-impl';
import { BoardsServiceProvider } from '../boards/boards-service-provider';
import { SketchContribution, Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry, TabBarToolbarRegistry } from './contribution';
@injectable()
@ -20,8 +20,8 @@ export class UploadSketch extends SketchContribution {
@inject(BoardsDataStore)
protected readonly boardsDataStore: BoardsDataStore;
@inject(BoardsServiceClientImpl)
protected readonly boardsServiceClientImpl: BoardsServiceClientImpl;
@inject(BoardsServiceProvider)
protected readonly boardsServiceClientImpl: BoardsServiceProvider;
@inject(OutputChannelManager)
protected readonly outputChannelManager: OutputChannelManager;

View File

@ -4,7 +4,7 @@ import { CoreService } from '../../common/protocol';
import { ArduinoMenus } from '../menu/arduino-menus';
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
import { BoardsDataStore } from '../boards/boards-data-store';
import { BoardsServiceClientImpl } from '../boards/boards-service-client-impl';
import { BoardsServiceProvider } from '../boards/boards-service-provider';
import { SketchContribution, Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry, TabBarToolbarRegistry } from './contribution';
@injectable()
@ -16,8 +16,8 @@ export class VerifySketch extends SketchContribution {
@inject(BoardsDataStore)
protected readonly boardsDataStore: BoardsDataStore;
@inject(BoardsServiceClientImpl)
protected readonly boardsServiceClientImpl: BoardsServiceClientImpl;
@inject(BoardsServiceProvider)
protected readonly boardsServiceClientImpl: BoardsServiceProvider;
@inject(OutputChannelManager)
protected readonly outputChannelManager: OutputChannelManager;

View File

@ -1,36 +0,0 @@
import { injectable, inject } from 'inversify';
import { Emitter, Event } from '@theia/core/lib/common/event';
import { ILogger } from '@theia/core/lib/common/logger';
import { MessageService } from '@theia/core/lib/common/message-service';
import { LocalStorageService } from '@theia/core/lib/browser/storage-service';
import { CoreServiceClient } from '../common/protocol';
@injectable()
export class CoreServiceClientImpl implements CoreServiceClient {
@inject(ILogger)
protected logger: ILogger;
@inject(MessageService)
protected messageService: MessageService;
@inject(LocalStorageService)
protected storageService: LocalStorageService;
protected readonly onIndexUpdatedEmitter = new Emitter<void>();
notifyIndexUpdated(): void {
this.info('Index has been updated.');
this.onIndexUpdatedEmitter.fire();
}
get onIndexUpdated(): Event<void> {
return this.onIndexUpdatedEmitter.event;
}
protected info(message: string): void {
this.messageService.info(message, { timeout: 3000 });
this.logger.info(message);
}
}

View File

@ -1,8 +1,7 @@
import { inject, injectable } from 'inversify';
import { LibraryPackage } from '../../common/protocol/library-service';
import { injectable, postConstruct, inject } from 'inversify';
import { LibraryPackage, LibraryService } from '../../common/protocol/library-service';
import { ListWidget } from '../widgets/component-list/list-widget';
import { ListItemRenderer } from '../widgets/component-list/list-item-renderer';
import { LibraryServiceProvider } from './library-service-provider';
@injectable()
export class LibraryListWidget extends ListWidget<LibraryPackage> {
@ -11,7 +10,7 @@ export class LibraryListWidget extends ListWidget<LibraryPackage> {
static WIDGET_LABEL = 'Library Manager';
constructor(
@inject(LibraryServiceProvider) protected service: LibraryServiceProvider,
@inject(LibraryService) protected service: LibraryService,
@inject(ListItemRenderer) protected itemRenderer: ListItemRenderer<LibraryPackage>) {
super({
@ -25,4 +24,13 @@ export class LibraryListWidget extends ListWidget<LibraryPackage> {
});
}
@postConstruct()
protected init(): void {
super.init();
this.toDispose.pushAll([
this.notificationCenter.onLibraryInstalled(() => this.refresh(undefined)),
this.notificationCenter.onLibraryUninstalled(() => this.refresh(undefined)),
]);
}
}

View File

@ -1,61 +0,0 @@
import { inject, injectable, postConstruct } from 'inversify';
import { JsonRpcProxy } from '@theia/core/lib/common/messaging/proxy-factory';
import { Event, Emitter } from '@theia/core/lib/common/event';
import { DisposableCollection } from '@theia/core/lib/common/disposable';
import { Searchable, InstalledEvent, UninstalledEvent } from '../../common/protocol';
import { LibraryPackage, LibraryServiceServer, LibraryService } from '../../common/protocol/library-service';
@injectable()
export class LibraryServiceProvider implements Required<LibraryService> {
@inject(LibraryServiceServer)
protected readonly server: JsonRpcProxy<LibraryServiceServer>;
protected readonly onLibraryPackageInstalledEmitter = new Emitter<InstalledEvent<LibraryPackage>>();
protected readonly onLibraryPackageUninstalledEmitter = new Emitter<UninstalledEvent<LibraryPackage>>();
protected readonly toDispose = new DisposableCollection(
this.onLibraryPackageInstalledEmitter,
this.onLibraryPackageUninstalledEmitter
);
@postConstruct()
protected init(): void {
this.server.setClient({
notifyInstalled: event => this.onLibraryPackageInstalledEmitter.fire(event),
notifyUninstalled: event => this.onLibraryPackageUninstalledEmitter.fire(event)
});
}
get onLibraryPackageInstalled(): Event<InstalledEvent<LibraryPackage>> {
return this.onLibraryPackageInstalledEmitter.event;
}
get onLibraryPackageUninstalled(): Event<InstalledEvent<LibraryPackage>> {
return this.onLibraryPackageUninstalledEmitter.event;
}
// #region remote library service API
async install(options: { item: LibraryPackage; version?: string | undefined; }): Promise<void> {
return this.server.install(options);
}
async list(options: LibraryService.List.Options): Promise<LibraryPackage[]> {
return this.server.list(options);
}
async uninstall(options: { item: LibraryPackage; }): Promise<void> {
return this.server.uninstall(options);
}
async search(options: Searchable.Options): Promise<LibraryPackage[]> {
return this.server.search(options);
}
// #endregion remote API
dispose(): void {
this.toDispose.dispose();
}
}

View File

@ -4,11 +4,12 @@ import { Emitter, Event } from '@theia/core/lib/common/event';
import { MessageService } from '@theia/core/lib/common/message-service';
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
import { MonitorService, MonitorConfig, MonitorError, Status, MonitorReadEvent } from '../../common/protocol/monitor-service';
import { BoardsServiceClientImpl } from '../boards/boards-service-client-impl';
import { BoardsServiceProvider } from '../boards/boards-service-provider';
import { Port, Board, BoardsService, AttachedBoardsChangeEvent } from '../../common/protocol/boards-service';
import { MonitorServiceClientImpl } from './monitor-service-client-impl';
import { BoardsConfig } from '../boards/boards-config';
import { MonitorModel } from './monitor-model';
import { NotificationCenter } from '../notification-center';
@injectable()
export class MonitorConnection {
@ -25,8 +26,11 @@ export class MonitorConnection {
@inject(BoardsService)
protected readonly boardsService: BoardsService;
@inject(BoardsServiceClientImpl)
protected boardsServiceClient: BoardsServiceClientImpl;
@inject(BoardsServiceProvider)
protected readonly boardsServiceProvider: BoardsServiceProvider;
@inject(NotificationCenter)
protected readonly notificationCenter: NotificationCenter;
@inject(MessageService)
protected messageService: MessageService;
@ -110,11 +114,11 @@ export class MonitorConnection {
}
}
});
this.boardsServiceClient.onBoardsConfigChanged(this.handleBoardConfigChange.bind(this));
this.boardsServiceClient.onAttachedBoardsChanged(event => {
this.boardsServiceProvider.onBoardsConfigChanged(this.handleBoardConfigChange.bind(this));
this.notificationCenter.onAttachedBoardsChanged(event => {
if (this.autoConnect && this.connected) {
const { boardsConfig } = this.boardsServiceClient;
if (this.boardsServiceClient.canUploadTo(boardsConfig, { silent: false })) {
const { boardsConfig } = this.boardsServiceProvider;
if (this.boardsServiceProvider.canUploadTo(boardsConfig, { silent: false })) {
const { attached } = AttachedBoardsChangeEvent.diff(event);
if (attached.boards.some(board => !!board.port && BoardsConfig.Config.sameAs(boardsConfig, board))) {
const { selectedBoard: board, selectedPort: port } = boardsConfig;
@ -128,7 +132,7 @@ export class MonitorConnection {
// Handles the `baudRate` changes by reconnecting if required.
this.monitorModel.onChange(({ property }) => {
if (property === 'baudRate' && this.autoConnect && this.connected) {
const { boardsConfig } = this.boardsServiceClient;
const { boardsConfig } = this.boardsServiceProvider;
this.handleBoardConfigChange(boardsConfig);
}
});
@ -154,7 +158,7 @@ export class MonitorConnection {
// We have to make sure the previous boards config has been restored.
// Otherwise, we might start the auto-connection without configured boards.
this.applicationState.reachedState('started_contributions').then(() => {
const { boardsConfig } = this.boardsServiceClient;
const { boardsConfig } = this.boardsServiceProvider;
this.handleBoardConfigChange(boardsConfig);
});
} else if (oldValue && !value) {
@ -227,7 +231,7 @@ export class MonitorConnection {
protected async handleBoardConfigChange(boardsConfig: BoardsConfig.Config): Promise<void> {
if (this.autoConnect) {
if (this.boardsServiceClient.canUploadTo(boardsConfig, { silent: false })) {
if (this.boardsServiceProvider.canUploadTo(boardsConfig, { silent: false })) {
// Instead of calling `getAttachedBoards` and filtering for `AttachedSerialBoard` we have to check the available ports.
// The connected board might be unknown. See: https://github.com/arduino/arduino-pro-ide/issues/127#issuecomment-563251881
this.boardsService.getAvailablePorts().then(ports => {

View File

@ -2,7 +2,7 @@ import { injectable, inject } from 'inversify';
import { Emitter, Event } from '@theia/core/lib/common/event';
import { MonitorConfig } from '../../common/protocol/monitor-service';
import { FrontendApplicationContribution, LocalStorageService } from '@theia/core/lib/browser';
import { BoardsServiceClientImpl } from '../boards/boards-service-client-impl';
import { BoardsServiceProvider } from '../boards/boards-service-provider';
@injectable()
export class MonitorModel implements FrontendApplicationContribution {
@ -12,8 +12,8 @@ export class MonitorModel implements FrontendApplicationContribution {
@inject(LocalStorageService)
protected readonly localStorageService: LocalStorageService;
@inject(BoardsServiceClientImpl)
protected readonly boardsServiceClient: BoardsServiceClientImpl;
@inject(BoardsServiceProvider)
protected readonly boardsServiceClient: BoardsServiceProvider;
protected readonly onChangeEmitter: Emitter<MonitorModel.State.Change<keyof MonitorModel.State>>;
protected _autoscroll: boolean;

View File

@ -0,0 +1,92 @@
import { inject, injectable, postConstruct } from 'inversify';
import { Emitter } from '@theia/core/lib/common/event';
import { JsonRpcProxy } from '@theia/core/lib/common/messaging/proxy-factory';
import { DisposableCollection } from '@theia/core/lib/common/disposable';
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
import { NotificationServiceClient, NotificationServiceServer } from '../common/protocol/notification-service';
import { AttachedBoardsChangeEvent, BoardsPackage, LibraryPackage, Config } from '../common/protocol';
@injectable()
export class NotificationCenter implements NotificationServiceClient, FrontendApplicationContribution {
@inject(NotificationServiceServer)
protected readonly server: JsonRpcProxy<NotificationServiceServer>;
protected readonly indexUpdatedEmitter = new Emitter<void>();
protected readonly daemonStartedEmitter = new Emitter<void>();
protected readonly daemonStoppedEmitter = new Emitter<void>();
protected readonly configChangedEmitter = new Emitter<{ config: Config | undefined }>();
protected readonly platformInstalledEmitter = new Emitter<{ item: BoardsPackage }>();
protected readonly platformUninstalledEmitter = new Emitter<{ item: BoardsPackage }>();
protected readonly libraryInstalledEmitter = new Emitter<{ item: LibraryPackage }>();
protected readonly libraryUninstalledEmitter = new Emitter<{ item: LibraryPackage }>();
protected readonly attachedBoardsChangedEmitter = new Emitter<AttachedBoardsChangeEvent>();
protected readonly toDispose = new DisposableCollection(
this.indexUpdatedEmitter,
this.daemonStartedEmitter,
this.daemonStoppedEmitter,
this.configChangedEmitter,
this.platformInstalledEmitter,
this.platformUninstalledEmitter,
this.libraryInstalledEmitter,
this.libraryUninstalledEmitter,
this.attachedBoardsChangedEmitter
);
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;
@postConstruct()
protected init(): void {
this.server.setClient(this);
}
onStop(): void {
this.toDispose.dispose();
}
notifyIndexUpdated(): void {
this.indexUpdatedEmitter.fire();
}
notifyDaemonStarted(): void {
this.daemonStartedEmitter.fire();
}
notifyDaemonStopped(): void {
this.daemonStoppedEmitter.fire();
}
notifyConfigChanged(event: { config: Config | undefined }): void {
this.configChangedEmitter.fire(event);
}
notifyPlatformInstalled(event: { item: BoardsPackage }): void {
this.platformInstalledEmitter.fire(event);
}
notifyPlatformUninstalled(event: { item: BoardsPackage }): void {
this.platformUninstalledEmitter.fire(event);
}
notifyLibraryInstalled(event: { item: LibraryPackage }): void {
this.libraryInstalledEmitter.fire(event);
}
notifyLibraryUninstalled(event: { item: LibraryPackage }): void {
this.libraryUninstalledEmitter.fire(event);
}
notifyAttachedBoardsChanged(event: AttachedBoardsChangeEvent): void {
this.attachedBoardsChangedEmitter.fire(event);
}
}

View File

@ -1,10 +1,10 @@
import { injectable, inject } from 'inversify';
import { inject, injectable } from 'inversify';
import { OutputContribution } from '@theia/output/lib/browser/output-contribution';
import { OutputChannelManager, OutputChannelSeverity } from '@theia/output/lib/common/output-channel';
import { ToolOutputServiceClient, ToolOutputMessage } from '../../common/protocol/tool-output-service';
import { OutputService, OutputMessage } from '../common/protocol/output-service';
@injectable()
export class ToolOutputServiceClientImpl implements ToolOutputServiceClient {
export class OutputServiceImpl implements OutputService {
@inject(OutputContribution)
protected outputContribution: OutputContribution;
@ -12,13 +12,12 @@ export class ToolOutputServiceClientImpl implements ToolOutputServiceClient {
@inject(OutputChannelManager)
protected outputChannelManager: OutputChannelManager;
onMessageReceived(message: ToolOutputMessage): void {
const { tool, chunk } = message;
const name = `Arduino: ${tool}`;
const channel = this.outputChannelManager.getChannel(name);
append(message: OutputMessage): void {
const { name, chunk } = message;
const channel = this.outputChannelManager.getChannel(`Arduino: ${name}`);
// Zen-mode: we do not reveal the output for daemon messages.
const show: Promise<any> = tool === 'daemon'
// This will open and reveal the view but won't show it. You will see the toggle bottom panel on the status bar
const show: Promise<any> = name === 'daemon'
// This will open and reveal the view but won't show it. You will see the toggle bottom panel on the status bar.
? this.outputContribution.openView({ activate: false, reveal: false })
// This will open, reveal but do not activate the Output view.
: Promise.resolve(channel.show({ preserveFocus: true }));
@ -26,7 +25,7 @@ export class ToolOutputServiceClientImpl implements ToolOutputServiceClient {
show.then(() => channel.append(chunk, this.toOutputSeverity(message)));
}
private toOutputSeverity(message: ToolOutputMessage): OutputChannelSeverity {
protected toOutputSeverity(message: OutputMessage): OutputChannelSeverity {
if (message.severity) {
switch (message.severity) {
case 'error': return OutputChannelSeverity.Error

View File

@ -6,20 +6,30 @@ import {
ApplicationConnectionStatusContribution as TheiaApplicationConnectionStatusContribution,
ConnectionStatus
} from '@theia/core/lib/browser/connection-status-service';
import { ArduinoDaemonClientImpl } from '../../arduino-daemon-client-impl';
import { ArduinoDaemon } from '../../../common/protocol';
import { NotificationCenter } from '../../notification-center';
@injectable()
export class FrontendConnectionStatusService extends TheiaFrontendConnectionStatusService {
@inject(ArduinoDaemonClientImpl)
protected readonly daemonClient: ArduinoDaemonClientImpl;
@inject(ArduinoDaemon)
protected readonly daemon: ArduinoDaemon;
@inject(NotificationCenter)
protected readonly notificationCenter: NotificationCenter;
protected isRunning = false;
@postConstruct()
protected init(): void {
protected async init(): Promise<void> {
this.schedulePing();
try {
this.isRunning = await this.daemon.isRunning();
} catch { }
this.notificationCenter.onDaemonStarted(() => this.isRunning = true);
this.notificationCenter.onDaemonStopped(() => this.isRunning = false);
this.wsConnectionProvider.onIncomingMessageActivity(() => {
// natural activity
this.updateStatus(this.daemonClient.isRunning);
this.updateStatus(this.isRunning);
this.schedulePing();
});
}
@ -29,22 +39,35 @@ export class FrontendConnectionStatusService extends TheiaFrontendConnectionStat
@injectable()
export class ApplicationConnectionStatusContribution extends TheiaApplicationConnectionStatusContribution {
@inject(ArduinoDaemonClientImpl)
protected readonly daemonClient: ArduinoDaemonClientImpl;
@inject(ArduinoDaemon)
protected readonly daemon: ArduinoDaemon;
@inject(NotificationCenter)
protected readonly notificationCenter: NotificationCenter;
protected isRunning = false;
@postConstruct()
protected async init(): Promise<void> {
try {
this.isRunning = await this.daemon.isRunning();
} catch { }
this.notificationCenter.onDaemonStarted(() => this.isRunning = true);
this.notificationCenter.onDaemonStopped(() => this.isRunning = false);
}
protected onStateChange(state: ConnectionStatus): void {
if (!this.daemonClient.isRunning && state === ConnectionStatus.ONLINE) {
if (!this.isRunning && state === ConnectionStatus.ONLINE) {
return;
}
super.onStateChange(state);
}
protected handleOffline(): void {
const { isRunning } = this.daemonClient;
this.statusBar.setElement('connection-status', {
alignment: StatusBarAlignment.LEFT,
text: isRunning ? 'Offline' : '$(bolt) CLI Daemon Offline',
tooltip: isRunning ? 'Cannot connect to the backend.' : 'Cannot connect to the CLI daemon.',
text: this.isRunning ? 'Offline' : '$(bolt) CLI Daemon Offline',
tooltip: this.isRunning ? 'Cannot connect to the backend.' : 'Cannot connect to the CLI daemon.',
priority: 5000
});
this.toDisposeOnOnline.push(Disposable.create(() => this.statusBar.removeElement('connection-status')));

View File

@ -10,17 +10,13 @@ import { Searchable } from '../../../common/protocol/searchable';
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
import { FilterableListContainer } from './filterable-list-container';
import { ListItemRenderer } from './list-item-renderer';
import { CoreServiceClientImpl } from '../../core-service-client-impl';
import { ArduinoDaemonClientImpl } from '../../arduino-daemon-client-impl';
import { NotificationCenter } from '../../notification-center';
@injectable()
export abstract class ListWidget<T extends ArduinoComponent> extends ReactWidget {
@inject(CoreServiceClientImpl)
protected readonly coreServiceClient: CoreServiceClientImpl;
@inject(ArduinoDaemonClientImpl)
protected readonly daemonClient: ArduinoDaemonClientImpl;
@inject(NotificationCenter)
protected readonly notificationCenter: NotificationCenter;
/**
* Do not touch or use it. It is for setting the focus on the `input` after the widget activation.
@ -49,9 +45,9 @@ export abstract class ListWidget<T extends ArduinoComponent> extends ReactWidget
protected init(): void {
this.update();
this.toDispose.pushAll([
this.coreServiceClient.onIndexUpdated(() => this.refresh(undefined)),
this.daemonClient.onDaemonStarted(() => this.refresh(undefined)),
this.daemonClient.onDaemonStopped(() => this.refresh(undefined))
this.notificationCenter.onIndexUpdated(() => this.refresh(undefined)),
this.notificationCenter.onDaemonStarted(() => this.refresh(undefined)),
this.notificationCenter.onDaemonStopped(() => this.refresh(undefined))
]);
}

View File

@ -1,13 +1,5 @@
import { JsonRpcServer } from '@theia/core/lib/common/messaging/proxy-factory';
export const ArduinoDaemonClient = Symbol('ArduinoDaemonClient');
export interface ArduinoDaemonClient {
notifyStarted(): void;
notifyStopped(): void;
}
export const ArduinoDaemonPath = '/services/arduino-daemon';
export const ArduinoDaemon = Symbol('ArduinoDaemon');
export interface ArduinoDaemon extends JsonRpcServer<ArduinoDaemonClient> {
export interface ArduinoDaemon {
isRunning(): Promise<boolean>;
}

View File

@ -1,8 +1,7 @@
import { isWindows, isOSX } from '@theia/core/lib/common/os';
import { JsonRpcServer } from '@theia/core/lib/common/messaging/proxy-factory';
import { naturalCompare } from './../utils';
import { Searchable } from './searchable';
import { Installable, InstallableClient } from './installable';
import { Installable } from './installable';
import { ArduinoComponent } from './arduino-component';
export interface AttachedBoardsChangeEvent {
@ -11,6 +10,45 @@ export interface AttachedBoardsChangeEvent {
}
export namespace AttachedBoardsChangeEvent {
export function isEmpty(event: AttachedBoardsChangeEvent): boolean {
const { detached, attached } = diff(event);
return !!detached.boards.length && !!detached.ports.length && !!attached.boards.length && !!attached.ports.length;
}
export function toString(event: AttachedBoardsChangeEvent): string {
let rows: string[] = [];
if (!isEmpty(event)) {
const { attached, detached } = diff(event);
const visitedAttachedPorts: Port[] = [];
const visitedDetachedPorts: Port[] = [];
for (const board of attached.boards) {
const port = board.port ? ` on ${Port.toString(board.port, { useLabel: true })}` : '';
rows.push(` - Attached board: ${Board.toString(board)}${port}`);
if (board.port) {
visitedAttachedPorts.push(board.port);
}
}
for (const board of detached.boards) {
const port = board.port ? ` from ${Port.toString(board.port, { useLabel: true })}` : '';
rows.push(` - Detached board: ${Board.toString(board)}${port}`);
if (board.port) {
visitedDetachedPorts.push(board.port);
}
}
for (const port of attached.ports) {
if (!visitedAttachedPorts.find(p => Port.sameAs(port, p))) {
rows.push(` - New port is available on ${Port.toString(port, { useLabel: true })}`);
}
}
for (const port of detached.ports) {
if (!visitedDetachedPorts.find(p => Port.sameAs(port, p))) {
rows.push(` - Port is no longer available on ${Port.toString(port, { useLabel: true })}`);
}
}
}
return rows.length ? rows.join('\n') : 'No changes.';
}
export function diff(event: AttachedBoardsChangeEvent): Readonly<{
attached: {
boards: Board[],
@ -45,14 +83,9 @@ export namespace AttachedBoardsChangeEvent {
}
export const BoardsServiceClient = Symbol('BoardsServiceClient');
export interface BoardsServiceClient extends InstallableClient<BoardsPackage> {
notifyAttachedBoardsChanged(event: AttachedBoardsChangeEvent): void;
}
export const BoardsServicePath = '/services/boards-service';
export const BoardsService = Symbol('BoardsService');
export interface BoardsService extends Installable<BoardsPackage>, Searchable<BoardsPackage>, JsonRpcServer<BoardsServiceClient> {
export interface BoardsService extends Installable<BoardsPackage>, Searchable<BoardsPackage> {
getAttachedBoards(): Promise<Board[]>;
getAvailablePorts(): Promise<Port[]>;
getBoardDetails(options: { fqbn: string }): Promise<BoardDetails>;
@ -185,9 +218,24 @@ export interface BoardsPackage extends ArduinoComponent {
readonly boards: Board[];
}
export namespace BoardsPackage {
export function equals(left: BoardsPackage, right: BoardsPackage): boolean {
return left.id === right.id;
}
export function contains(selectedBoard: Board, { id, boards }: BoardsPackage): boolean {
if (boards.some(board => Board.sameAs(board, selectedBoard))) {
return true;
}
if (selectedBoard.fqbn) {
const [platform, architecture] = selectedBoard.fqbn.split(':');
if (platform && architecture) {
return `${platform}:${architecture}` === id;
}
}
return false;
}
}
export interface Board {

View File

@ -1,14 +1,6 @@
import { JsonRpcServer } from '@theia/core/lib/common/messaging/proxy-factory';
export const ConfigServiceClient = Symbol('ConfigServiceClient');
export interface ConfigServiceClient {
notifyConfigChanged(config: Config): void;
notifyInvalidConfig(): void;
}
export const ConfigServicePath = '/services/config-service';
export const ConfigService = Symbol('ConfigService');
export interface ConfigService extends JsonRpcServer<ConfigServiceClient> {
export interface ConfigService {
getVersion(): Promise<string>;
getConfiguration(): Promise<Config>;
getCliConfigFileUri(): Promise<string>;

View File

@ -1,14 +1,8 @@
import { JsonRpcServer } from '@theia/core/lib/common/messaging/proxy-factory';
import { Programmer } from './boards-service';
export const CoreServiceClient = Symbol('CoreServiceClient');
export interface CoreServiceClient {
notifyIndexUpdated(): void;
}
export const CoreServicePath = '/services/core-service';
export const CoreService = Symbol('CoreService');
export interface CoreService extends JsonRpcServer<CoreServiceClient> {
export interface CoreService {
compile(options: CoreService.Compile.Options): Promise<void>;
upload(options: CoreService.Upload.Options): Promise<void>;
burnBootloader(options: CoreService.Bootloader.Options): Promise<void>;

View File

@ -9,6 +9,7 @@ export * from './library-service';
export * from './monitor-service';
export * from './searchable';
export * from './sketches-service';
export * from './tool-output-service';
export * from './examples-service';
export * from './executable-service';
export * from './output-service';
export * from './notification-service';

View File

@ -1,17 +1,6 @@
import { naturalCompare } from './../utils';
import { ArduinoComponent } from './arduino-component';
export interface InstalledEvent<T extends ArduinoComponent> {
readonly item: Readonly<T>;
}
export interface UninstalledEvent<T extends ArduinoComponent> {
readonly item: Readonly<T>;
}
export interface InstallableClient<T extends ArduinoComponent> {
notifyInstalled(event: InstalledEvent<T>): void
notifyUninstalled(event: UninstalledEvent<T>): void
}
export interface Installable<T extends ArduinoComponent> {
/**
* If `options.version` is specified, that will be installed. Otherwise, `item.availableVersions[0]`.

View File

@ -1,22 +1,13 @@
import { JsonRpcServer } from '@theia/core/lib/common/messaging/proxy-factory';
import { Searchable } from './searchable';
import { Installable } from './installable';
import { ArduinoComponent } from './arduino-component';
import { Installable, InstallableClient } from './installable';
export const LibraryServicePath = '/services/library-service';
export const LibraryService = Symbol('LibraryService');
export interface LibraryService extends Installable<LibraryPackage>, Searchable<LibraryPackage> {
install(options: { item: LibraryPackage, version?: Installable.Version }): Promise<void>;
list(options: LibraryService.List.Options): Promise<LibraryPackage[]>;
}
export const LibraryServiceClient = Symbol('LibraryServiceClient');
export interface LibraryServiceClient extends InstallableClient<LibraryPackage> {
}
export const LibraryServiceServerPath = '/services/library-service-server';
export const LibraryServiceServer = Symbol('LibraryServiceServer');
export interface LibraryServiceServer extends LibraryService, JsonRpcServer<LibraryServiceClient> {
}
export namespace LibraryService {
export namespace List {
export interface Options {
@ -26,31 +17,38 @@ export namespace LibraryService {
}
export enum LibraryLocation {
/**
* In the `libraries` subdirectory of the Arduino IDE installation.
*/
IDE_BUILTIN = 0,
/**
* In the `libraries` subdirectory of the user directory (sketchbook).
*/
USER = 1,
/**
* In the `libraries` subdirectory of a platform.
*/
PLATFORM_BUILTIN = 2,
/**
* When `LibraryLocation` is used in a context where a board is specified, this indicates the library is in the `libraries`
* subdirectory of a platform referenced by the board's platform.
*/
REFERENCED_PLATFORM_BUILTIN = 3
}
export interface LibraryPackage extends ArduinoComponent {
/**
* Same as [`Library#real_name`](https://arduino.github.io/arduino-cli/latest/rpc/commands/#library).
* Should be used for the UI, and `name` is used to uniquely identify a library. It does not have an ID.
*/
readonly label: string;
/**
* An array of string that should be included into the `ino` file if this library is used.
* For example, including `SD` will prepend `#include <SD.h>` to the `ino` file. While including `Bridge`

View File

@ -0,0 +1,22 @@
import { LibraryPackage } from './library-service';
import { JsonRpcServer } from '@theia/core/lib/common/messaging/proxy-factory';
import { BoardsPackage, AttachedBoardsChangeEvent } from './boards-service';
import { Config } from './config-service';
export interface NotificationServiceClient {
notifyIndexUpdated(): void;
notifyDaemonStarted(): 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;
}
export const NotificationServicePath = '/services/notification-service';
export const NotificationServiceServer = Symbol('NotificationServiceServer');
export interface NotificationServiceServer extends Required<NotificationServiceClient>, JsonRpcServer<NotificationServiceClient> {
disposeClient(client: NotificationServiceClient): void;
}

View File

@ -0,0 +1,11 @@
export interface OutputMessage {
readonly name: string;
readonly chunk: string;
readonly severity?: 'error' | 'warning' | 'info';
}
export const OutputServicePath = '/services/output-service';
export const OutputService = Symbol('OutputService');
export interface OutputService {
append(message: OutputMessage): void;
}

View File

@ -1,22 +0,0 @@
import { JsonRpcServer } from '@theia/core';
export interface ToolOutputMessage {
readonly tool: string;
readonly chunk: string;
readonly severity?: 'error' | 'warning' | 'info';
}
export const ToolOutputServiceServer = Symbol('ToolOutputServiceServer');
export interface ToolOutputServiceServer extends JsonRpcServer<ToolOutputServiceClient> {
append(message: ToolOutputMessage): void;
disposeClient(client: ToolOutputServiceClient): void;
}
export const ToolOutputServiceClient = Symbol('ToolOutputServiceClient');
export interface ToolOutputServiceClient {
onMessageReceived(message: ToolOutputMessage): void;
}
export namespace ToolOutputService {
export const SERVICE_PATH = '/tool-output-service';
}

View File

@ -9,7 +9,7 @@ import { Event, Emitter } from '@theia/core/lib/common/event';
import { environment } from '@theia/application-package/lib/environment';
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
import { BackendApplicationContribution } from '@theia/core/lib/node/backend-application';
import { ArduinoDaemon, ArduinoDaemonClient, ToolOutputServiceServer } from '../common/protocol';
import { ArduinoDaemon, NotificationServiceServer } from '../common/protocol';
import { DaemonLog } from './daemon-log';
import { CLI_CONFIG } from './cli-config';
import { getExecPath, spawnCommand } from './exec-util';
@ -21,13 +21,12 @@ export class ArduinoDaemonImpl implements ArduinoDaemon, BackendApplicationContr
@named('daemon')
protected readonly logger: ILogger
@inject(ToolOutputServiceServer)
protected readonly toolOutputService: ToolOutputServiceServer;
@inject(EnvVariablesServer)
protected readonly envVariablesServer: EnvVariablesServer;
protected readonly clients: Array<ArduinoDaemonClient> = [];
@inject(NotificationServiceServer)
protected readonly notificationService: NotificationServiceServer;
protected readonly toDispose = new DisposableCollection();
protected readonly onDaemonStartedEmitter = new Emitter<void>();
protected readonly onDaemonStoppedEmitter = new Emitter<void>();
@ -42,37 +41,6 @@ export class ArduinoDaemonImpl implements ArduinoDaemon, BackendApplicationContr
this.startDaemon();
}
onStop(): void {
this.dispose();
}
// JSON-RPC proxy
setClient(client: ArduinoDaemonClient | undefined): void {
if (client) {
if (this._running) {
client.notifyStarted()
} else {
client.notifyStopped();
}
this.clients.push(client);
}
}
dispose(): void {
this.toDispose.dispose();
this.clients.length = 0;
}
disposeClient(client: ArduinoDaemonClient): void {
const index = this.clients.indexOf(client);
if (index === -1) {
this.logger.warn('Could not dispose client. It was not registered or was already disposed.');
} else {
this.clients.splice(index, 1);
}
}
// Daemon API
async isRunning(): Promise<boolean> {
@ -184,7 +152,7 @@ export class ArduinoDaemonImpl implements ArduinoDaemon, BackendApplicationContr
if (code === 0 || signal === 'SIGINT' || signal === 'SIGKILL') {
this.onData('Daemon has stopped.');
} else {
this.onData(`Daemon exited with ${typeof code === 'undefined' ? `signal '${signal}'` : `exit code: ${code}`}.`, { useOutput: false });
this.onData(`Daemon exited with ${typeof code === 'undefined' ? `signal '${signal}'` : `exit code: ${code}`}.`);
}
});
daemon.on('error', error => {
@ -198,9 +166,7 @@ export class ArduinoDaemonImpl implements ArduinoDaemon, BackendApplicationContr
this._running = true;
this._ready.resolve();
this.onDaemonStartedEmitter.fire();
for (const client of this.clients) {
client.notifyStarted();
}
this.notificationService.notifyDaemonStarted();
}
protected fireDaemonStopped(): void {
@ -211,15 +177,10 @@ export class ArduinoDaemonImpl implements ArduinoDaemon, BackendApplicationContr
this._ready.reject(); // Reject all pending.
this._ready = new Deferred<void>();
this.onDaemonStoppedEmitter.fire();
for (const client of this.clients) {
client.notifyStopped();
}
this.notificationService.notifyDaemonStopped();
}
protected onData(message: string, options: { useOutput: boolean } = { useOutput: true }): void {
if (options.useOutput) {
this.toolOutputService.append({ tool: 'daemon', chunk: DaemonLog.toPrettyString(message) });
}
protected onData(message: string): void {
DaemonLog.log(this.logger, message);
}

View File

@ -5,130 +5,169 @@ import { ContainerModule } from 'inversify';
import { ArduinoDaemonImpl } from './arduino-daemon-impl';
import { ILogger } from '@theia/core/lib/common/logger';
import { BackendApplicationContribution } from '@theia/core/lib/node/backend-application';
import { LibraryServiceServerPath, LibraryServiceServer, LibraryServiceClient } from '../common/protocol/library-service';
import { BoardsService, BoardsServicePath, BoardsServiceClient } from '../common/protocol/boards-service';
import { LibraryServiceServerImpl } from './library-service-server-impl';
import { LibraryService, LibraryServicePath } from '../common/protocol/library-service';
import { BoardsService, BoardsServicePath } from '../common/protocol/boards-service';
import { LibraryServiceImpl } from './library-service-server-impl';
import { BoardsServiceImpl } from './boards-service-impl';
import { CoreServiceImpl } from './core-service-impl';
import { CoreService, CoreServicePath, CoreServiceClient } from '../common/protocol/core-service';
import { CoreService, CoreServicePath } from '../common/protocol/core-service';
import { ConnectionContainerModule } from '@theia/core/lib/node/messaging/connection-container-module';
import { CoreClientProvider } from './core-client-provider';
import { ToolOutputService, ToolOutputServiceClient, ToolOutputServiceServer } from '../common/protocol/tool-output-service';
import { ConnectionHandler, JsonRpcConnectionHandler } from '@theia/core';
import { ToolOutputServiceServerImpl } from './tool-output-service-impl';
import { DefaultWorkspaceServerExt } from './default-workspace-server-ext';
import { WorkspaceServer } from '@theia/workspace/lib/common';
import { ConnectionHandler, JsonRpcConnectionHandler, JsonRpcProxy } from '@theia/core';
import { DefaultWorkspaceServer } from './theia/workspace/default-workspace-server';
import { WorkspaceServer as TheiaWorkspaceServer } from '@theia/workspace/lib/common';
import { SketchesServiceImpl } from './sketches-service-impl';
import { SketchesService, SketchesServicePath } from '../common/protocol/sketches-service';
import { ConfigService, ConfigServicePath, ConfigServiceClient } from '../common/protocol/config-service';
import { ArduinoDaemon, ArduinoDaemonPath, ArduinoDaemonClient } from '../common/protocol/arduino-daemon';
import { ConfigService, ConfigServicePath } from '../common/protocol/config-service';
import { ArduinoDaemon, ArduinoDaemonPath } from '../common/protocol/arduino-daemon';
import { MonitorServiceImpl } from './monitor/monitor-service-impl';
import { MonitorService, MonitorServicePath, MonitorServiceClient } from '../common/protocol/monitor-service';
import { MonitorClientProvider } from './monitor/monitor-client-provider';
import { ConfigServiceImpl } from './config-service-impl';
import { ArduinoHostedPluginReader } from './arduino-plugin-reader';
import { HostedPluginReader } from '@theia/plugin-ext/lib/hosted/node/plugin-reader';
import { HostedPluginReader } from './theia/plugin-ext/plugin-reader';
import { HostedPluginReader as TheiaHostedPluginReader } from '@theia/plugin-ext/lib/hosted/node/plugin-reader';
import { ConfigFileValidator } from './config-file-validator';
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
import { ArduinoEnvVariablesServer } from './arduino-env-variables-server';
import { EnvVariablesServer as TheiaEnvVariablesServer } from '@theia/core/lib/common/env-variables';
import { EnvVariablesServer } from './theia/env-variables/env-variables-server';
import { NodeFileSystemExt } from './node-filesystem-ext';
import { FileSystemExt, FileSystemExtPath } from '../common/protocol/filesystem-ext';
import { ExamplesServiceImpl } from './examples-service-impl';
import { ExamplesService, ExamplesServicePath } from '../common/protocol/examples-service';
import { ExecutableService, ExecutableServicePath } from '../common/protocol/executable-service';
import { ExecutableServiceImpl } from './executable-service-impl';
import { OutputServicePath, OutputService } from '../common/protocol/output-service';
import { NotificationServiceServerImpl } from './notification-service-server';
import { NotificationServiceServer, NotificationServiceClient, NotificationServicePath } from '../common/protocol';
export default new ContainerModule((bind, unbind, isBound, rebind) => {
rebind(EnvVariablesServer).to(ArduinoEnvVariablesServer).inSingletonScope();
bind(ConfigFileValidator).toSelf().inSingletonScope();
// XXX: The config service must start earlier than the daemon, hence the binding order does matter.
// Shared config service
bind(ConfigFileValidator).toSelf().inSingletonScope();
bind(ConfigServiceImpl).toSelf().inSingletonScope();
bind(ConfigService).toService(ConfigServiceImpl);
// Note: The config service must start earlier than the daemon, hence the binding order of the BA contribution does matter.
bind(BackendApplicationContribution).toService(ConfigServiceImpl);
bind(ConnectionHandler).toDynamicValue(context =>
new JsonRpcConnectionHandler<ConfigServiceClient>(ConfigServicePath, client => {
const server = context.container.get<ConfigServiceImpl>(ConfigServiceImpl);
server.setClient(client);
client.onDidCloseConnection(() => server.disposeClient(client));
return server;
})
).inSingletonScope();
bind(ConnectionHandler).toDynamicValue(context => new JsonRpcConnectionHandler(ConfigServicePath, () => context.container.get(ConfigService))).inSingletonScope();
// Shared daemon
bind(ArduinoDaemonImpl).toSelf().inSingletonScope();
bind(ArduinoDaemon).toService(ArduinoDaemonImpl);
bind(BackendApplicationContribution).toService(ArduinoDaemonImpl);
bind(ConnectionHandler).toDynamicValue(context =>
new JsonRpcConnectionHandler<ArduinoDaemonClient>(ArduinoDaemonPath, async client => {
const server = context.container.get<ArduinoDaemonImpl>(ArduinoDaemonImpl);
server.setClient(client);
client.onDidCloseConnection(() => server.disposeClient(client));
return server;
})
).inSingletonScope();
bind(ConnectionHandler).toDynamicValue(context => new JsonRpcConnectionHandler(ArduinoDaemonPath, () => context.container.get(ArduinoDaemon))).inSingletonScope();
// Shared examples service
bind(ExamplesServiceImpl).toSelf().inSingletonScope();
bind(ExamplesService).toService(ExamplesServiceImpl);
bind(ConnectionHandler).toDynamicValue(context => new JsonRpcConnectionHandler(ExamplesServicePath, () => context.container.get(ExamplesService))).inSingletonScope();
// Examples service. One per backend, each connected FE gets a proxy.
bind(ConnectionContainerModule).toConstantValue(ConnectionContainerModule.create(({ bind, bindBackendService }) => {
// const ExamplesServiceProxy = Symbol('ExamplesServiceProxy');
// bind(ExamplesServiceProxy).toDynamicValue(ctx => new Proxy(ctx.container.get(ExamplesService), {}));
// bindBackendService(ExamplesServicePath, ExamplesServiceProxy);
bind(ExamplesServiceImpl).toSelf().inSingletonScope();
bind(ExamplesService).toService(ExamplesServiceImpl);
bindBackendService(ExamplesServicePath, ExamplesService);
}));
// Exposes the executable paths/URIs to the frontend
bind(ExecutableServiceImpl).toSelf().inSingletonScope();
bind(ExecutableService).toService(ExecutableServiceImpl);
bind(ConnectionHandler).toDynamicValue(context => new JsonRpcConnectionHandler(ExecutableServicePath, () => context.container.get(ExecutableService))).inSingletonScope();
// Library service
bind(LibraryServiceServerImpl).toSelf().inSingletonScope();
bind(LibraryServiceServer).toService(LibraryServiceServerImpl);
bind(ConnectionHandler).toDynamicValue(context =>
new JsonRpcConnectionHandler<LibraryServiceClient>(LibraryServiceServerPath, client => {
const server = context.container.get<LibraryServiceServerImpl>(LibraryServiceServerImpl);
server.setClient(client);
client.onDidCloseConnection(() => server.dispose());
return server;
})
).inSingletonScope();
// Library service. Singleton per backend, each connected FE gets its proxy.
bind(ConnectionContainerModule).toConstantValue(ConnectionContainerModule.create(({ bind, bindBackendService }) => {
// const LibraryServiceProxy = Symbol('LibraryServiceProxy');
// bind(LibraryServiceProxy).toDynamicValue(ctx => new Proxy(ctx.container.get(LibraryService), {}));
// bindBackendService(LibraryServicePath, LibraryServiceProxy);
bind(LibraryServiceImpl).toSelf().inSingletonScope();
bind(LibraryService).toService(LibraryServiceImpl);
bindBackendService(LibraryServicePath, LibraryService);
}));
// Shred sketches service
bind(SketchesServiceImpl).toSelf().inSingletonScope();
bind(SketchesService).toService(SketchesServiceImpl);
bind(ConnectionHandler).toDynamicValue(context => new JsonRpcConnectionHandler(SketchesServicePath, () => context.container.get(SketchesService))).inSingletonScope();
// Boards service
const boardsServiceConnectionModule = ConnectionContainerModule.create(async ({ bind, bindBackendService }) => {
// Boards service. One singleton per backend that does the board and port polling. Each connected FE gets its proxy.
bind(ConnectionContainerModule).toConstantValue(ConnectionContainerModule.create(({ bind, bindBackendService }) => {
// const BoardsServiceProxy = Symbol('BoardsServiceProxy');
// bind(BoardsServiceProxy).toDynamicValue(ctx => new Proxy(ctx.container.get(BoardsService), {}));
// bindBackendService(BoardsServicePath, BoardsServiceProxy);
bind(BoardsServiceImpl).toSelf().inSingletonScope();
bind(BoardsService).toService(BoardsServiceImpl);
bindBackendService<BoardsService, BoardsServiceClient>(BoardsServicePath, BoardsService, (service, client) => {
service.setClient(client);
bindBackendService<BoardsServiceImpl, JsonRpcProxy<object>>(BoardsServicePath, BoardsService, (service, client) => {
client.onDidCloseConnection(() => service.dispose());
return service;
});
});
bind(ConnectionContainerModule).toConstantValue(boardsServiceConnectionModule);
}));
// Shared Arduino core client provider service for the backend.
bind(CoreClientProvider).toSelf().inSingletonScope();
// Core service -> `verify` and `upload`. One per Theia connection.
const connectionConnectionModule = ConnectionContainerModule.create(({ bind, bindBackendService }) => {
// Core service -> `verify` and `upload`. Singleton per BE, each FE connection gets its proxy.
bind(ConnectionContainerModule).toConstantValue(ConnectionContainerModule.create(({ bind, bindBackendService }) => {
// const CoreServiceProxy = Symbol('CoreServiceProxy');
// bind(CoreServiceProxy).toDynamicValue(ctx => new Proxy(ctx.container.get(CoreService), {}));
// bindBackendService(CoreServicePath, CoreServiceProxy);
bind(CoreServiceImpl).toSelf().inSingletonScope();
bind(CoreService).toService(CoreServiceImpl);
bindBackendService(BoardsServicePath, BoardsService);
bindBackendService<CoreService, CoreServiceClient>(CoreServicePath, CoreService, (service, client) => {
bindBackendService(CoreServicePath, CoreService);
}));
// #region Theia customizations
bind(DefaultWorkspaceServer).toSelf().inSingletonScope();
rebind(TheiaWorkspaceServer).toService(DefaultWorkspaceServer);
bind(EnvVariablesServer).toSelf().inSingletonScope();
rebind(TheiaEnvVariablesServer).toService(EnvVariablesServer);
bind(HostedPluginReader).toSelf().inSingletonScope();
rebind(TheiaHostedPluginReader).toService(HostedPluginReader);
// #endregion Theia customizations
// Shared monitor client provider service for the backend.
bind(MonitorClientProvider).toSelf().inSingletonScope();
bind(MonitorServiceImpl).toSelf().inSingletonScope();
bind(MonitorService).toService(MonitorServiceImpl);
bind(ConnectionContainerModule).toConstantValue(ConnectionContainerModule.create(({ bind, bindBackendService }) => {
const MonitorServiceProxy = Symbol('MonitorServiceProxy');
bind(MonitorServiceProxy).toDynamicValue(ctx => new Proxy(ctx.container.get(MonitorService), {}));
bindBackendService<MonitorService, MonitorServiceClient>(MonitorServicePath, MonitorServiceProxy, (service, client) => {
service.setClient(client);
client.onDidCloseConnection(() => service.dispose());
return service;
});
});
bind(ConnectionContainerModule).toConstantValue(connectionConnectionModule);
}));
// Tool output service -> feedback from the daemon, compile and flash
bind(ToolOutputServiceServerImpl).toSelf().inSingletonScope();
bind(ToolOutputServiceServer).toService(ToolOutputServiceServerImpl);
// Set up cpp extension
if (!process.env.CPP_CLANGD_COMMAND) {
const segments = ['..', '..', 'build'];
if (os.platform() === 'win32') {
segments.push('clangd.exe');
} else {
segments.push('bin');
segments.push('clangd');
}
const clangdCommand = join(__dirname, ...segments);
if (fs.existsSync(clangdCommand)) {
process.env.CPP_CLANGD_COMMAND = clangdCommand;
}
}
// File-system extension for mapping paths to URIs
bind(NodeFileSystemExt).toSelf().inSingletonScope();
bind(FileSystemExt).toService(NodeFileSystemExt);
bind(ConnectionHandler).toDynamicValue(context => new JsonRpcConnectionHandler(FileSystemExtPath, () => context.container.get(FileSystemExt))).inSingletonScope();
// Output service per connection.
bind(ConnectionContainerModule).toConstantValue(ConnectionContainerModule.create(({ bindFrontendService }) => {
bindFrontendService(OutputServicePath, OutputService);
}));
// Notify all connected frontend instances
bind(NotificationServiceServerImpl).toSelf().inSingletonScope();
bind(NotificationServiceServer).toService(NotificationServiceServerImpl);
bind(ConnectionHandler).toDynamicValue(context =>
new JsonRpcConnectionHandler<ToolOutputServiceClient>(ToolOutputService.SERVICE_PATH, client => {
const server = context.container.get<ToolOutputServiceServer>(ToolOutputServiceServer);
new JsonRpcConnectionHandler<NotificationServiceClient>(NotificationServicePath, client => {
const server = context.container.get<NotificationServiceServer>(NotificationServiceServer);
server.setClient(client);
client.onDidCloseConnection(() => server.disposeClient(client));
return server;
@ -153,52 +192,10 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
return parentLogger.child('config');
}).inSingletonScope().whenTargetNamed('config');
// Default workspace server extension to initialize and use a fallback workspace.
// If nothing was set previously.
bind(DefaultWorkspaceServerExt).toSelf().inSingletonScope();
rebind(WorkspaceServer).toService(DefaultWorkspaceServerExt);
// Shared monitor client provider service for the backend.
bind(MonitorClientProvider).toSelf().inSingletonScope();
// Connection scoped service for the serial monitor.
const monitorServiceConnectionModule = ConnectionContainerModule.create(({ bind, bindBackendService }) => {
bind(MonitorServiceImpl).toSelf().inSingletonScope();
bind(MonitorService).toService(MonitorServiceImpl);
bindBackendService<MonitorService, MonitorServiceClient>(MonitorServicePath, MonitorService, (service, client) => {
service.setClient(client);
client.onDidCloseConnection(() => service.dispose());
return service;
});
});
bind(ConnectionContainerModule).toConstantValue(monitorServiceConnectionModule);
// Logger for the monitor service.
bind(ILogger).toDynamicValue(ctx => {
const parentLogger = ctx.container.get<ILogger>(ILogger);
return parentLogger.child('monitor-service');
}).inSingletonScope().whenTargetNamed('monitor-service');
// Set up cpp extension
if (!process.env.CPP_CLANGD_COMMAND) {
const segments = ['..', '..', 'build'];
if (os.platform() === 'win32') {
segments.push('clangd.exe');
} else {
segments.push('bin');
segments.push('clangd');
}
const clangdCommand = join(__dirname, ...segments);
if (fs.existsSync(clangdCommand)) {
process.env.CPP_CLANGD_COMMAND = clangdCommand;
}
}
bind(ArduinoHostedPluginReader).toSelf().inSingletonScope();
rebind(HostedPluginReader).toService(ArduinoHostedPluginReader);
// File-system extension for mapping paths to URIs
bind(NodeFileSystemExt).toSelf().inSingletonScope();
bind(FileSystemExt).toService(NodeFileSystemExt);
bind(ConnectionHandler).toDynamicValue(context => new JsonRpcConnectionHandler(FileSystemExtPath, () => context.container.get(FileSystemExt))).inSingletonScope();
});

View File

@ -1,19 +1,22 @@
import { injectable, inject, postConstruct, named } from 'inversify';
import { ILogger } from '@theia/core/lib/common/logger';
import { Deferred } from '@theia/core/lib/common/promise-util';
import { BoardsService, BoardsPackage, Board, BoardsServiceClient, Port, BoardDetails, Tool, ConfigOption, ConfigValue, Programmer } from '../common/protocol';
import {
BoardsService,
BoardsPackage, Board, Port, BoardDetails, Tool, ConfigOption, ConfigValue, Programmer, OutputService, NotificationServiceServer, AttachedBoardsChangeEvent
} from '../common/protocol';
import {
PlatformSearchReq, PlatformSearchResp, PlatformInstallReq, PlatformInstallResp, PlatformListReq,
PlatformListResp, Platform, PlatformUninstallResp, PlatformUninstallReq
} from './cli-protocol/commands/core_pb';
import { CoreClientProvider } from './core-client-provider';
import { BoardListReq, BoardListResp, BoardDetailsReq, BoardDetailsResp } from './cli-protocol/commands/board_pb';
import { ToolOutputServiceServer } from '../common/protocol/tool-output-service';
import { Installable } from '../common/protocol/installable';
import { ListProgrammersAvailableForUploadReq, ListProgrammersAvailableForUploadResp } from './cli-protocol/commands/upload_pb';
import { Disposable } from '@theia/core/lib/common/disposable';
@injectable()
export class BoardsServiceImpl implements BoardsService {
export class BoardsServiceImpl implements BoardsService, Disposable {
@inject(ILogger)
protected logger: ILogger;
@ -25,8 +28,11 @@ export class BoardsServiceImpl implements BoardsService {
@inject(CoreClientProvider)
protected readonly coreClientProvider: CoreClientProvider;
@inject(ToolOutputServiceServer)
protected readonly toolOutputService: ToolOutputServiceServer;
@inject(OutputService)
protected readonly outputService: OutputService;
@inject(NotificationServiceServer)
protected readonly notificationService: NotificationServiceServer;
protected discoveryInitialized = false;
protected discoveryTimer: NodeJS.Timer | undefined;
@ -38,7 +44,6 @@ export class BoardsServiceImpl implements BoardsService {
protected attachedBoards: Board[] = [];
protected availablePorts: Port[] = [];
protected started = new Deferred<void>();
protected client: BoardsServiceClient | undefined;
@postConstruct()
protected async init(): Promise<void> {
@ -49,19 +54,19 @@ export class BoardsServiceImpl implements BoardsService {
const update = (oldBoards: Board[], newBoards: Board[], oldPorts: Port[], newPorts: Port[], message: string) => {
this.attachedBoards = newBoards;
this.availablePorts = newPorts;
this.discoveryLogger.info(`${message} - Discovered boards: ${JSON.stringify(newBoards)} and available ports: ${JSON.stringify(newPorts)}`);
if (this.client) {
this.client.notifyAttachedBoardsChanged({
oldState: {
boards: oldBoards,
ports: oldPorts
},
newState: {
boards: newBoards,
ports: newPorts
}
});
}
const event = {
oldState: {
boards: oldBoards,
ports: oldPorts
},
newState: {
boards: newBoards,
ports: newPorts
}
};
this.discoveryLogger.info(`${message}`);
this.discoveryLogger.info(`${AttachedBoardsChangeEvent.toString(event)}`);
this.notificationService.notifyAttachedBoardsChanged(event);
}
const sortedBoards = boards.sort(Board.compare);
const sortedPorts = ports.sort(Port.compare);
@ -103,16 +108,13 @@ export class BoardsServiceImpl implements BoardsService {
}, 1000);
}
setClient(client: BoardsServiceClient | undefined): void {
this.client = client;
}
dispose(): void {
this.logger.info('>>> Disposing boards service...');
if (this.discoveryTimer !== undefined) {
this.logger.info('>>> Disposing the boards discovery...');
clearInterval(this.discoveryTimer);
this.logger.info('<<< Disposed the boards discovery.');
}
this.client = undefined;
this.logger.info('<<< Disposed boards service.');
}
@ -213,14 +215,28 @@ export class BoardsServiceImpl implements BoardsService {
const detailsReq = new BoardDetailsReq();
detailsReq.setInstance(instance);
detailsReq.setFqbn(fqbn);
const detailsResp = await new Promise<BoardDetailsResp>((resolve, reject) => client.boardDetails(detailsReq, (err, resp) => {
const detailsResp = await new Promise<BoardDetailsResp | undefined>((resolve, reject) => client.boardDetails(detailsReq, (err, resp) => {
if (err) {
// Required cores are not installed manually: https://github.com/arduino/arduino-cli/issues/954
if (err.message.indexOf('missing platform release') !== -1 && err.message.indexOf('referenced by board') !== -1) {
resolve(undefined);
return;
}
reject(err);
return;
}
resolve(resp);
}));
if (!detailsResp) {
return {
fqbn,
configOptions: [],
programmers: [],
requiredTools: []
};
}
const requiredTools = detailsResp.getToolsdependenciesList().map(t => <Tool>{
name: t.getName(),
packager: t.getPackager(),
@ -391,18 +407,17 @@ export class BoardsServiceImpl implements BoardsService {
resp.on('data', (r: PlatformInstallResp) => {
const prog = r.getProgress();
if (prog && prog.getFile()) {
this.toolOutputService.append({ tool: 'board download', chunk: `downloading ${prog.getFile()}\n` });
this.outputService.append({ name: 'board download', chunk: `downloading ${prog.getFile()}\n` });
}
});
await new Promise<void>((resolve, reject) => {
resp.on('end', resolve);
resp.on('error', reject);
});
if (this.client) {
const items = await this.search({});
const updated = items.find(other => BoardsPackage.equals(other, item)) || item;
this.client.notifyInstalled({ item: updated });
}
const items = await this.search({});
const updated = items.find(other => BoardsPackage.equals(other, item)) || item;
this.notificationService.notifyPlatformInstalled({ item: updated });
console.info('<<< Boards package installation done.', item);
}
@ -426,7 +441,7 @@ export class BoardsServiceImpl implements BoardsService {
const resp = client.platformUninstall(req);
resp.on('data', (_: PlatformUninstallResp) => {
if (!logged) {
this.toolOutputService.append({ tool: 'board uninstall', chunk: `uninstalling ${item.id}\n` });
this.outputService.append({ name: 'board uninstall', chunk: `uninstalling ${item.id}\n` });
logged = true;
}
})
@ -434,10 +449,9 @@ export class BoardsServiceImpl implements BoardsService {
resp.on('end', resolve);
resp.on('error', reject);
});
if (this.client) {
// Here, unlike at `install` we send out the argument `item`. Otherwise, we would not know about the board FQBN.
this.client.notifyUninstalled({ item });
}
// Here, unlike at `install` we send out the argument `item`. Otherwise, we would not know about the board FQBN.
this.notificationService.notifyPlatformUninstalled({ item });
console.info('<<< Boards package uninstallation done.', item);
}

View File

@ -8,7 +8,7 @@ import { ILogger } from '@theia/core/lib/common/logger';
import { FileUri } from '@theia/core/lib/node/file-uri';
import { Event, Emitter } from '@theia/core/lib/common/event';
import { BackendApplicationContribution } from '@theia/core/lib/node/backend-application';
import { ConfigService, Config, ConfigServiceClient } from '../common/protocol';
import { ConfigService, Config, NotificationServiceServer } from '../common/protocol';
import * as fs from './fs-extra';
import { spawnCommand } from './exec-util';
import { RawData } from './cli-protocol/settings/settings_pb';
@ -38,10 +38,12 @@ export class ConfigServiceImpl implements BackendApplicationContribution, Config
@inject(ArduinoDaemonImpl)
protected readonly daemon: ArduinoDaemonImpl;
@inject(NotificationServiceServer)
protected readonly notificationService: NotificationServiceServer;
protected updating = false;
protected config: Config;
protected cliConfig: DefaultCliConfig | undefined;
protected clients: Array<ConfigServiceClient> = [];
protected ready = new Deferred<void>();
protected readonly configChangeEmitter = new Emitter<Config>();
@ -94,25 +96,6 @@ export class ConfigServiceImpl implements BackendApplicationContribution, Config
return this.getConfiguration().then(({ sketchDirUri }) => new URI(sketchDirUri).isEqualOrParent(new URI(uri)));
}
setClient(client: ConfigServiceClient | undefined): void {
if (client) {
this.clients.push(client);
}
}
dispose(): void {
this.clients.length = 0;
}
disposeClient(client: ConfigServiceClient): void {
const index = this.clients.indexOf(client);
if (index === -1) {
this.logger.warn('Could not dispose client. It was not registered or was already disposed.');
} else {
this.clients.splice(index, 1);
}
}
protected async loadCliConfig(): Promise<DefaultCliConfig | undefined> {
const cliConfigFileUri = await this.getCliConfigFileUri();
const cliConfigPath = FileUri.fsPath(cliConfigFileUri);
@ -214,9 +197,7 @@ export class ConfigServiceImpl implements BackendApplicationContribution, Config
this.cliConfig = cliConfig;
this.config = config;
this.configChangeEmitter.fire(this.config);
for (const client of this.clients) {
client.notifyConfigChanged(this.config);
}
this.notificationService.notifyConfigChanged({ config: this.config });
}).finally(() => this.updating = false);
} catch (err) {
this.logger.error('Failed to update the daemon with the current CLI configuration.', err);
@ -227,15 +208,11 @@ export class ConfigServiceImpl implements BackendApplicationContribution, Config
}
protected fireConfigChanged(config: Config): void {
for (const client of this.clients) {
client.notifyConfigChanged(config);
}
this.notificationService.notifyConfigChanged({ config });
}
protected fireInvalidConfig(): void {
for (const client of this.clients) {
client.notifyInvalidConfig();
}
this.notificationService.notifyConfigChanged({ config: undefined });
}
protected async unwatchCliConfig(): Promise<void> {

View File

@ -1,26 +1,21 @@
import * as grpc from '@grpc/grpc-js';
import { inject, injectable } from 'inversify';
import { Event, Emitter } from '@theia/core/lib/common/event';
import { ToolOutputServiceServer } from '../common/protocol';
import { GrpcClientProvider } from './grpc-client-provider';
import { ArduinoCoreClient } from './cli-protocol/commands/commands_grpc_pb';
import * as commandsGrpcPb from './cli-protocol/commands/commands_grpc_pb';
import { Instance } from './cli-protocol/commands/common_pb';
import { InitReq, InitResp, UpdateIndexReq, UpdateIndexResp, UpdateLibrariesIndexResp, UpdateLibrariesIndexReq } from './cli-protocol/commands/commands_pb';
import { NotificationServiceServer } from '../common/protocol';
@injectable()
export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Client> {
@inject(ToolOutputServiceServer)
protected readonly toolOutputService: ToolOutputServiceServer;
@inject(NotificationServiceServer)
protected readonly notificationService: NotificationServiceServer;
protected readonly onIndexUpdatedEmitter = new Emitter<void>();
protected readonly onClientReadyEmitter = new Emitter<void>();
get onIndexUpdated(): Event<void> {
return this.onIndexUpdatedEmitter.event;
}
get onClientReady(): Event<void> {
return this.onClientReadyEmitter.event;
}
@ -76,11 +71,11 @@ export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Cl
indexUpdateSucceeded = true;
break;
} catch (e) {
this.toolOutputService.append({ tool: 'daemon', chunk: `Error while updating index in attempt ${i}: ${e}`, severity: 'error' });
console.error(`Error while updating index in attempt ${i}.`, e);
}
}
if (!indexUpdateSucceeded) {
this.toolOutputService.append({ tool: 'daemon', chunk: 'Was unable to update the index. Please restart to try again.', severity: 'error' });
console.error('Could not update the index. Please restart to try again.');
}
let libIndexUpdateSucceeded = true;
@ -90,15 +85,15 @@ export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Cl
libIndexUpdateSucceeded = true;
break;
} catch (e) {
this.toolOutputService.append({ tool: 'daemon', chunk: `Error while updating library index in attempt ${i}: ${e}`, severity: 'error' });
console.error(`Error while updating library index in attempt ${i}.`, e);
}
}
if (!libIndexUpdateSucceeded) {
this.toolOutputService.append({ tool: 'daemon', chunk: `Was unable to update the library index. Please restart to try again.`, severity: 'error' });
console.error('Could not update the library index. Please restart to try again.');
}
if (indexUpdateSucceeded && libIndexUpdateSucceeded) {
this.onIndexUpdatedEmitter.fire();
this.notificationService.notifyIndexUpdated();
}
}
@ -116,12 +111,12 @@ export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Cl
if (progress.getCompleted()) {
if (file) {
if (/\s/.test(file)) {
this.toolOutputService.append({ tool: 'daemon', chunk: `${file} completed.\n` });
console.log(`${file} completed.`);
} else {
this.toolOutputService.append({ tool: 'daemon', chunk: `Download of '${file}' completed.\n'` });
console.log(`Download of '${file}' completed.`);
}
} else {
this.toolOutputService.append({ tool: 'daemon', chunk: `The library index has been successfully updated.\n'` });
console.log('The library index has been successfully updated.');
}
file = undefined;
}
@ -147,12 +142,12 @@ export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Cl
if (progress.getCompleted()) {
if (file) {
if (/\s/.test(file)) {
this.toolOutputService.append({ tool: 'daemon', chunk: `${file} completed.\n` });
console.log(`${file} completed.`);
} else {
this.toolOutputService.append({ tool: 'daemon', chunk: `Download of '${file}' completed.\n'` });
console.log(`Download of '${file}' completed.`);
}
} else {
this.toolOutputService.append({ tool: 'daemon', chunk: `The index has been successfully updated.\n'` });
console.log('The index has been successfully updated.');
}
file = undefined;
}

View File

@ -1,12 +1,12 @@
import { inject, injectable, postConstruct } from 'inversify';
import { FileUri } from '@theia/core/lib/node/file-uri';
import { CoreService, CoreServiceClient } from '../common/protocol/core-service';
import { inject, injectable } from 'inversify';
import { dirname } from 'path';
import { CoreService } from '../common/protocol/core-service';
import { CompileReq, CompileResp } from './cli-protocol/commands/compile_pb';
import { BoardsService } from '../common/protocol/boards-service';
import { CoreClientProvider } from './core-client-provider';
import * as path from 'path';
import { ToolOutputServiceServer } from '../common/protocol/tool-output-service';
import { UploadReq, UploadResp, BurnBootloaderReq, BurnBootloaderResp } from './cli-protocol/commands/upload_pb';
import { OutputService } from '../common/protocol/output-service';
import { NotificationServiceServer } from '../common/protocol';
@injectable()
export class CoreServiceImpl implements CoreService {
@ -14,29 +14,17 @@ export class CoreServiceImpl implements CoreService {
@inject(CoreClientProvider)
protected readonly coreClientProvider: CoreClientProvider;
@inject(OutputService)
protected readonly outputService: OutputService;
@inject(BoardsService)
protected readonly boardsService: BoardsService;
@inject(ToolOutputServiceServer)
protected readonly toolOutputService: ToolOutputServiceServer;
protected client: CoreServiceClient | undefined;
@postConstruct()
protected init(): void {
this.coreClientProvider.onIndexUpdated(() => {
if (this.client) {
this.client.notifyIndexUpdated();
}
})
}
@inject(NotificationServiceServer)
protected readonly notificationService: NotificationServiceServer;
async compile(options: CoreService.Compile.Options): Promise<void> {
this.toolOutputService.append({ tool: 'compile', chunk: 'Compiling...\n' + JSON.stringify(options, null, 2) + '\n--------------------------\n' });
this.outputService.append({ name: 'compile', chunk: 'Compiling...\n' + JSON.stringify(options, null, 2) + '\n--------------------------\n' });
const { sketchUri, fqbn } = options;
const sketchFilePath = FileUri.fsPath(sketchUri);
const sketchpath = path.dirname(sketchFilePath);
const sketchpath = dirname(sketchFilePath);
const coreClient = await this.coreClientProvider.client();
if (!coreClient) {
@ -61,25 +49,25 @@ export class CoreServiceImpl implements CoreService {
try {
await new Promise<void>((resolve, reject) => {
result.on('data', (cr: CompileResp) => {
this.toolOutputService.append({ tool: 'compile', chunk: Buffer.from(cr.getOutStream_asU8()).toString() });
this.toolOutputService.append({ tool: 'compile', chunk: Buffer.from(cr.getErrStream_asU8()).toString() });
this.outputService.append({ name: 'compile', chunk: Buffer.from(cr.getOutStream_asU8()).toString() });
this.outputService.append({ name: 'compile', chunk: Buffer.from(cr.getErrStream_asU8()).toString() });
});
result.on('error', error => reject(error));
result.on('end', () => resolve());
});
this.toolOutputService.append({ tool: 'compile', chunk: '\n--------------------------\nCompilation complete.\n' });
this.outputService.append({ name: 'compile', chunk: '\n--------------------------\nCompilation complete.\n' });
} catch (e) {
this.toolOutputService.append({ tool: 'compile', chunk: `Compilation error: ${e}\n`, severity: 'error' });
this.outputService.append({ name: 'compile', chunk: `Compilation error: ${e}\n`, severity: 'error' });
throw e;
}
}
async upload(options: CoreService.Upload.Options): Promise<void> {
await this.compile(options);
this.toolOutputService.append({ tool: 'upload', chunk: 'Uploading...\n' + JSON.stringify(options, null, 2) + '\n--------------------------\n' });
this.outputService.append({ name: 'upload', chunk: 'Uploading...\n' + JSON.stringify(options, null, 2) + '\n--------------------------\n' });
const { sketchUri, fqbn } = options;
const sketchFilePath = FileUri.fsPath(sketchUri);
const sketchpath = path.dirname(sketchFilePath);
const sketchpath = dirname(sketchFilePath);
const coreClient = await this.coreClientProvider.client();
if (!coreClient) {
@ -106,15 +94,15 @@ export class CoreServiceImpl implements CoreService {
try {
await new Promise<void>((resolve, reject) => {
result.on('data', (resp: UploadResp) => {
this.toolOutputService.append({ tool: 'upload', chunk: Buffer.from(resp.getOutStream_asU8()).toString() });
this.toolOutputService.append({ tool: 'upload', chunk: Buffer.from(resp.getErrStream_asU8()).toString() });
this.outputService.append({ name: 'upload', chunk: Buffer.from(resp.getOutStream_asU8()).toString() });
this.outputService.append({ name: 'upload', chunk: Buffer.from(resp.getErrStream_asU8()).toString() });
});
result.on('error', error => reject(error));
result.on('end', () => resolve());
});
this.toolOutputService.append({ tool: 'upload', chunk: '\n--------------------------\nUpload complete.\n' });
this.outputService.append({ name: 'upload', chunk: '\n--------------------------\nUpload complete.\n' });
} catch (e) {
this.toolOutputService.append({ tool: 'upload', chunk: `Upload error: ${e}\n`, severity: 'error' });
this.outputService.append({ name: 'upload', chunk: `Upload error: ${e}\n`, severity: 'error' });
throw e;
}
}
@ -141,24 +129,16 @@ export class CoreServiceImpl implements CoreService {
try {
await new Promise<void>((resolve, reject) => {
result.on('data', (resp: BurnBootloaderResp) => {
this.toolOutputService.append({ tool: 'bootloader', chunk: Buffer.from(resp.getOutStream_asU8()).toString() });
this.toolOutputService.append({ tool: 'bootloader', chunk: Buffer.from(resp.getErrStream_asU8()).toString() });
this.outputService.append({ name: 'bootloader', chunk: Buffer.from(resp.getOutStream_asU8()).toString() });
this.outputService.append({ name: 'bootloader', chunk: Buffer.from(resp.getErrStream_asU8()).toString() });
});
result.on('error', error => reject(error));
result.on('end', () => resolve());
});
} catch (e) {
this.toolOutputService.append({ tool: 'bootloader', chunk: `Error while burning the bootloader: ${e}\n`, severity: 'error' });
this.outputService.append({ name: 'bootloader', chunk: `Error while burning the bootloader: ${e}\n`, severity: 'error' });
throw e;
}
}
setClient(client: CoreServiceClient | undefined): void {
this.client = client;
}
dispose(): void {
this.client = undefined;
}
}

View File

@ -6,7 +6,7 @@ import { notEmpty } from '@theia/core/lib/common/objects';
import { Sketch } from '../common/protocol/sketches-service';
import { SketchesServiceImpl } from './sketches-service-impl';
import { ExamplesService, ExampleContainer } from '../common/protocol/examples-service';
import { LibraryServiceServer, LibraryLocation, LibraryPackage } from '../common/protocol';
import { LibraryLocation, LibraryPackage, LibraryService } from '../common/protocol';
import { ConfigServiceImpl } from './config-service-impl';
@injectable()
@ -15,8 +15,8 @@ export class ExamplesServiceImpl implements ExamplesService {
@inject(SketchesServiceImpl)
protected readonly sketchesService: SketchesServiceImpl;
@inject(LibraryServiceServer)
protected readonly libraryService: LibraryServiceServer;
@inject(LibraryService)
protected readonly libraryService: LibraryService;
@inject(ConfigServiceImpl)
protected readonly configService: ConfigServiceImpl;
@ -44,7 +44,7 @@ export class ExamplesServiceImpl implements ExamplesService {
const current: ExampleContainer[] = [];
const any: ExampleContainer[] = [];
if (fqbn) {
const packages = await this.libraryService.list({ fqbn });
const packages: LibraryPackage[] = await this.libraryService.list({ fqbn });
for (const pkg of packages) {
const container = await this.tryGroupExamples(pkg);
const { location } = pkg;

View File

@ -1,5 +1,5 @@
import { injectable, inject, postConstruct } from 'inversify';
import { LibraryPackage, LibraryServiceClient, LibraryServiceServer } from '../common/protocol/library-service';
import { LibraryPackage, LibraryService } from '../common/protocol/library-service';
import { CoreClientProvider } from './core-client-provider';
import {
LibrarySearchReq,
@ -14,14 +14,14 @@ import {
LibraryUninstallResp,
Library
} from './cli-protocol/commands/lib_pb';
import { ToolOutputServiceServer } from '../common/protocol/tool-output-service';
import { Installable } from '../common/protocol/installable';
import { ILogger, notEmpty } from '@theia/core';
import { Deferred } from '@theia/core/lib/common/promise-util';
import { FileUri } from '@theia/core/lib/node';
import { OutputService, NotificationServiceServer } from '../common/protocol';
@injectable()
export class LibraryServiceServerImpl implements LibraryServiceServer {
export class LibraryServiceImpl implements LibraryService {
@inject(ILogger)
protected logger: ILogger;
@ -29,11 +29,13 @@ export class LibraryServiceServerImpl implements LibraryServiceServer {
@inject(CoreClientProvider)
protected readonly coreClientProvider: CoreClientProvider;
@inject(ToolOutputServiceServer)
protected readonly toolOutputService: ToolOutputServiceServer;
@inject(OutputService)
protected readonly outputService: OutputService;
@inject(NotificationServiceServer)
protected readonly notificationServer: NotificationServiceServer;
protected ready = new Deferred<void>();
protected client: LibraryServiceClient | undefined;
@postConstruct()
protected init(): void {
@ -108,7 +110,31 @@ export class LibraryServiceServerImpl implements LibraryServiceServer {
req.setFqbn(fqbn);
}
const resp = await new Promise<LibraryListResp>((resolve, reject) => client.libraryList(req, ((error, resp) => !!error ? reject(error) : resolve(resp))));
const resp = await new Promise<LibraryListResp | undefined>((resolve, reject) => {
client.libraryList(req, ((error, r) => {
if (error) {
const { message } = error;
// Required core dependency is missing.
// https://github.com/arduino/arduino-cli/issues/954
if (message.indexOf('missing platform release') !== -1 && message.indexOf('referenced by board') !== -1) {
resolve(undefined);
return;
}
// The core for the board is not installed, `lib list` cannot be filtered based on FQBN.
// https://github.com/arduino/arduino-cli/issues/955
if (message.indexOf('platform') !== -1 && message.indexOf('is not installed') !== -1) {
resolve(undefined);
return;
}
reject(error);
return;
}
resolve(r);
}));
});
if (!resp) {
return [];
}
return resp.getInstalledLibraryList().map(item => {
const library = item.getLibrary();
if (!library) {
@ -151,7 +177,7 @@ export class LibraryServiceServerImpl implements LibraryServiceServer {
resp.on('data', (r: LibraryInstallResp) => {
const prog = r.getProgress();
if (prog) {
this.toolOutputService.append({ tool: 'library', chunk: `downloading ${prog.getFile()}: ${prog.getCompleted()}%\n` });
this.outputService.append({ name: 'library', chunk: `downloading ${prog.getFile()}: ${prog.getCompleted()}%\n` });
}
});
await new Promise<void>((resolve, reject) => {
@ -159,12 +185,9 @@ export class LibraryServiceServerImpl implements LibraryServiceServer {
resp.on('error', reject);
});
if (this.client) {
const items = await this.search({});
const updated = items.find(other => LibraryPackage.equals(other, item)) || item;
this.client.notifyInstalled({ item: updated });
}
const items = await this.search({});
const updated = items.find(other => LibraryPackage.equals(other, item)) || item;
this.notificationServer.notifyLibraryInstalled({ item: updated });
console.info('<<< Library package installation done.', item);
}
@ -186,7 +209,7 @@ export class LibraryServiceServerImpl implements LibraryServiceServer {
const resp = client.libraryUninstall(req);
resp.on('data', (_: LibraryUninstallResp) => {
if (!logged) {
this.toolOutputService.append({ tool: 'library', chunk: `uninstalling ${item.name}:${item.installedVersion}%\n` });
this.outputService.append({ name: 'library', chunk: `uninstalling ${item.name}:${item.installedVersion}%\n` });
logged = true;
}
});
@ -194,19 +217,13 @@ export class LibraryServiceServerImpl implements LibraryServiceServer {
resp.on('end', resolve);
resp.on('error', reject);
});
if (this.client) {
this.client.notifyUninstalled({ item });
}
console.info('<<< Library package uninstallation done.', item);
}
setClient(client: LibraryServiceClient | undefined): void {
this.client = client;
this.notificationServer.notifyLibraryUninstalled({ item });
console.info('<<< Library package uninstallation done.', item);
}
dispose(): void {
this.logger.info('>>> Disposing library service...');
this.client = undefined;
this.logger.info('<<< Disposed library service.');
}

View File

@ -0,0 +1,65 @@
import { injectable } from 'inversify';
import { NotificationServiceServer, NotificationServiceClient, AttachedBoardsChangeEvent, BoardsPackage, LibraryPackage, Config } from '../common/protocol';
@injectable()
export class NotificationServiceServerImpl implements NotificationServiceServer {
protected readonly clients: NotificationServiceClient[] = [];
notifyIndexUpdated(): void {
this.clients.forEach(client => client.notifyIndexUpdated());
}
notifyDaemonStarted(): void {
this.clients.forEach(client => client.notifyDaemonStarted());
}
notifyDaemonStopped(): void {
this.clients.forEach(client => client.notifyDaemonStopped());
}
notifyPlatformInstalled(event: { item: BoardsPackage }): void {
this.clients.forEach(client => client.notifyPlatformInstalled(event));
}
notifyPlatformUninstalled(event: { item: BoardsPackage }): void {
this.clients.forEach(client => client.notifyPlatformUninstalled(event));
}
notifyLibraryInstalled(event: { item: LibraryPackage }): void {
this.clients.forEach(client => client.notifyLibraryInstalled(event));
}
notifyLibraryUninstalled(event: { item: LibraryPackage }): void {
this.clients.forEach(client => client.notifyLibraryUninstalled(event));
}
notifyAttachedBoardsChanged(event: AttachedBoardsChangeEvent): void {
this.clients.forEach(client => client.notifyAttachedBoardsChanged(event));
}
notifyConfigChanged(event: { config: Config | undefined }): void {
this.clients.forEach(client => client.notifyConfigChanged(event));
}
setClient(client: NotificationServiceClient): void {
this.clients.push(client);
}
disposeClient(client: NotificationServiceClient): void {
const index = this.clients.indexOf(client);
if (index === -1) {
console.warn(`Could not dispose notification service client. It was not registered.`);
return;
}
this.clients.splice(index, 1);
}
dispose(): void {
for (const client of this.clients) {
this.disposeClient(client);
}
this.clients.length = 0;
}
}

View File

@ -2,11 +2,11 @@ import { join } from 'path';
import { homedir } from 'os';
import { injectable } from 'inversify';
import { FileUri } from '@theia/core/lib/node/file-uri';
import { EnvVariablesServerImpl } from '@theia/core/lib/node/env-variables/env-variables-server';
import { BackendApplicationConfigProvider } from '@theia/core/lib/node/backend-application-config-provider';
import { EnvVariablesServerImpl as TheiaEnvVariablesServerImpl } from '@theia/core/lib/node/env-variables/env-variables-server';
@injectable()
export class ArduinoEnvVariablesServer extends EnvVariablesServerImpl {
export class EnvVariablesServer extends TheiaEnvVariablesServerImpl {
protected readonly configDirUri = Promise.resolve(FileUri.create(join(homedir(), BackendApplicationConfigProvider.get().configDirName)).toString());

View File

@ -1,11 +1,11 @@
import { injectable, inject } from 'inversify';
import { HostedPluginReader } from '@theia/plugin-ext/lib/hosted/node/plugin-reader';
import { HostedPluginReader as TheiaHostedPluginReader } from '@theia/plugin-ext/lib/hosted/node/plugin-reader';
import { PluginPackage, PluginContribution } from '@theia/plugin-ext/lib/common/plugin-protocol';
import { CLI_CONFIG } from './cli-config';
import { ConfigServiceImpl } from './config-service-impl';
import { CLI_CONFIG } from '../../cli-config';
import { ConfigServiceImpl } from '../../config-service-impl';
@injectable()
export class ArduinoHostedPluginReader extends HostedPluginReader {
export class HostedPluginReader extends TheiaHostedPluginReader {
@inject(ConfigServiceImpl)
protected readonly configService: ConfigServiceImpl;

View File

@ -1,10 +1,10 @@
import { injectable, inject } from 'inversify';
import { ILogger } from '@theia/core';
import { DefaultWorkspaceServer } from '@theia/workspace/lib/node/default-workspace-server';
import { ConfigService } from '../common/protocol/config-service';
import { ILogger } from '@theia/core/lib/common/logger';
import { DefaultWorkspaceServer as TheiaDefaultWorkspaceServer } from '@theia/workspace/lib/node/default-workspace-server';
import { ConfigService } from '../../../common/protocol/config-service';
@injectable()
export class DefaultWorkspaceServerExt extends DefaultWorkspaceServer {
export class DefaultWorkspaceServer extends TheiaDefaultWorkspaceServer {
@inject(ConfigService)
protected readonly configService: ConfigService;

View File

@ -1,40 +0,0 @@
import { injectable } from 'inversify';
import { ToolOutputServiceServer, ToolOutputServiceClient, ToolOutputMessage } from '../common/protocol/tool-output-service';
@injectable()
export class ToolOutputServiceServerImpl implements ToolOutputServiceServer {
protected clients: ToolOutputServiceClient[] = [];
append(message: ToolOutputMessage): void {
if (!message.chunk) {
return;
}
for (const client of this.clients) {
client.onMessageReceived(message);
}
}
setClient(client: ToolOutputServiceClient | undefined): void {
if (!client) {
return;
}
this.clients.push(client);
}
disposeClient(client: ToolOutputServiceClient): void {
const index = this.clients.indexOf(client);
if (index === -1) {
console.warn(`Could not dispose tools output client. It was not registered.`);
return;
}
this.clients.splice(index, 1);
}
dispose(): void {
for (const client of this.clients) {
this.disposeClient(client);
}
this.clients.length = 0;
}
}

View File

@ -11,9 +11,10 @@ import { MessageClient } from '@theia/core/lib/common/message-service-protocol';
import { MessageService } from '@theia/core/lib/common/message-service';
import { StorageService } from '@theia/core/lib/browser/storage-service';
import { DisposableCollection } from '@theia/core/lib/common/disposable';
import { BoardsService, Board, Port, BoardsPackage, BoardDetails, BoardsServiceClient } from '../../common/protocol';
import { BoardsServiceClientImpl, AvailableBoard } from '../../browser/boards/boards-service-client-impl';
import { BoardsService, Board, Port, BoardsPackage, BoardDetails } from '../../common/protocol';
import { BoardsServiceProvider, AvailableBoard } from '../../browser/boards/boards-service-provider';
import { BoardsConfig } from '../../browser/boards/boards-config';
import { NotificationCenter } from '../../browser/notification-center';
// tslint:disable: no-unused-expression
@ -33,14 +34,14 @@ describe('boards-service-client-impl', () => {
let stub: sinon.SinonStub;
let server: MockBoardsService;
let client: BoardsServiceClientImpl;
let client: BoardsServiceProvider;
let notificationCenter: NotificationCenter;
beforeEach(() => {
stub = sinon.stub(os, 'isOSX').value(true);
const container = init();
server = container.get(MockBoardsService);
client = container.get(BoardsServiceClientImpl);
server.setClient(client);
client = container.get(BoardsServiceProvider);
});
afterEach(() => {
@ -165,7 +166,7 @@ function init(): Container {
container.bind(ILogger).toService(MockLogger);
container.bind(MockStorageService).toSelf();
container.bind(StorageService).toService(MockStorageService);
container.bind(BoardsServiceClientImpl).toSelf();
container.bind(BoardsServiceProvider).toSelf();
container.bind(MessageClient).toSelf().inSingletonScope();
container.bind(MessageService).toSelf().inSingletonScope();
return container;
@ -293,10 +294,6 @@ export class MockBoardsService implements BoardsService {
this.client = undefined;
}
setClient(client: BoardsServiceClient | undefined): void {
this.client = client;
}
}
@injectable()