Files
arduino-ide/arduino-ide-extension/src/browser/boards/boards-data-store.ts
2021-02-11 09:59:46 +01:00

212 lines
7.5 KiB
TypeScript

import { injectable, inject, named } from 'inversify';
import { ILogger } from '@theia/core/lib/common/logger';
import { deepClone } from '@theia/core/lib/common/objects';
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 { BoardsService, ConfigOption, Installable, BoardDetails, Programmer } from '../../common/protocol';
import { NotificationCenter } from '../notification-center';
@injectable()
export class BoardsDataStore implements FrontendApplicationContribution {
@inject(ILogger)
@named('store')
protected readonly logger: ILogger;
@inject(BoardsService)
protected readonly boardsService: BoardsService;
@inject(NotificationCenter)
protected readonly notificationCenter: NotificationCenter;
@inject(LocalStorageService)
protected readonly storageService: LocalStorageService;
protected readonly onChangedEmitter = new Emitter<void>();
onStart(): void {
this.notificationCenter.onPlatformInstalled(async ({ item }) => {
const { installedVersion: version } = item;
if (!version) {
return;
}
let shouldFireChanged = false;
for (const fqbn of item.boards.map(({ fqbn }) => fqbn).filter(notEmpty).filter(fqbn => !!fqbn)) {
const key = this.getStorageKey(fqbn, version);
let data = await this.storageService.getData<ConfigOption[] | undefined>(key);
if (!data || !data.length) {
const details = await this.getBoardDetailsSafe(fqbn);
if (details) {
data = details.configOptions;
if (data.length) {
await this.storageService.setData(key, data);
shouldFireChanged = true;
}
}
}
}
if (shouldFireChanged) {
this.fireChanged();
}
});
}
get onChanged(): Event<void> {
return this.onChangedEmitter.event;
}
async appendConfigToFqbn(
fqbn: string | undefined,
boardsPackageVersion: MaybePromise<Installable.Version | undefined> = this.getBoardsPackageVersion(fqbn)): Promise<string | undefined> {
if (!fqbn) {
return undefined;
}
const { configOptions } = await this.getData(fqbn, boardsPackageVersion);
return ConfigOption.decorate(fqbn, configOptions);
}
async getData(
fqbn: string | undefined,
boardsPackageVersion: MaybePromise<Installable.Version | undefined> = this.getBoardsPackageVersion(fqbn)): Promise<BoardsDataStore.Data> {
if (!fqbn) {
return BoardsDataStore.Data.EMPTY;
}
const version = await boardsPackageVersion;
if (!version) {
return BoardsDataStore.Data.EMPTY;
}
const key = this.getStorageKey(fqbn, version);
let data = await this.storageService.getData<BoardsDataStore.Data | undefined>(key, undefined);
if (BoardsDataStore.Data.is(data)) {
return data;
}
const boardDetails = await this.getBoardDetailsSafe(fqbn);
if (!boardDetails) {
return BoardsDataStore.Data.EMPTY;
}
data = { configOptions: boardDetails.configOptions, programmers: boardDetails.programmers };
await this.storageService.setData(key, data);
return data;
}
async selectProgrammer(
{ fqbn, selectedProgrammer }: { fqbn: string, selectedProgrammer: Programmer },
boardsPackageVersion: MaybePromise<Installable.Version | undefined> = this.getBoardsPackageVersion(fqbn)): Promise<boolean> {
const data = deepClone(await this.getData(fqbn, boardsPackageVersion));
const { programmers } = data;
if (!programmers.find(p => Programmer.equals(selectedProgrammer, p))) {
return false;
}
const version = await boardsPackageVersion;
if (!version) {
return false;
}
await this.setData({ fqbn, data: { ...data, selectedProgrammer }, version });
this.fireChanged();
return true;
}
async selectConfigOption(
{ fqbn, option, selectedValue }: { fqbn: string, option: string, selectedValue: string },
boardsPackageVersion: MaybePromise<Installable.Version | undefined> = this.getBoardsPackageVersion(fqbn)): Promise<boolean> {
const data = deepClone(await this.getData(fqbn, boardsPackageVersion));
const { configOptions } = data;
const configOption = configOptions.find(c => c.option === option);
if (!configOption) {
return false;
}
let updated = false;
for (const value of configOption.values) {
if (value.value === selectedValue) {
(value as any).selected = true;
updated = true;
} else {
(value as any).selected = false;
}
}
if (!updated) {
return false;
}
const version = await boardsPackageVersion;
if (!version) {
return false;
}
await this.setData({ fqbn, data, version });
this.fireChanged();
return true;
}
protected async setData(
{ fqbn, data, version }: { fqbn: string, data: BoardsDataStore.Data, version: Installable.Version }): Promise<void> {
const key = this.getStorageKey(fqbn, version);
return this.storageService.setData(key, data);
}
protected getStorageKey(fqbn: string, version: Installable.Version): string {
return `.arduinoIDE-configOptions-${version}-${fqbn}`;
}
protected async getBoardDetailsSafe(fqbn: string): Promise<BoardDetails | undefined> {
try {
const details = this.boardsService.getBoardDetails({ fqbn });
return details;
} catch (err) {
if (err instanceof Error && err.message.includes('loading board data') && err.message.includes('is not installed')) {
this.logger.warn(`The boards package is not installed for board with FQBN: ${fqbn}`);
} else {
this.logger.error(`An unexpected error occurred while retrieving the board details for ${fqbn}.`, err);
}
return undefined;
}
}
protected fireChanged(): void {
this.onChangedEmitter.fire();
}
protected async getBoardsPackageVersion(fqbn: string | undefined): Promise<Installable.Version | undefined> {
if (!fqbn) {
return undefined;
}
const boardsPackage = await this.boardsService.getContainerBoardPackage({ fqbn });
if (!boardsPackage) {
return undefined;
}
return boardsPackage.installedVersion;
}
}
export namespace BoardsDataStore {
export interface Data {
readonly configOptions: ConfigOption[];
readonly programmers: Programmer[];
readonly selectedProgrammer?: Programmer;
}
export namespace Data {
export const EMPTY: Data = {
configOptions: [],
programmers: []
};
export function is(arg: any): arg is Data {
return !!arg
&& 'configOptions' in arg && Array.isArray(arg['configOptions'])
&& 'programmers' in arg && Array.isArray(arg['programmers'])
}
}
}