Made boards installable

This commit is contained in:
Christian Weichel 2019-05-06 15:48:33 +02:00
parent 201351fea8
commit c48d80b137
10 changed files with 135 additions and 39 deletions

View File

@ -12,6 +12,10 @@ export class ComponentListItem extends React.Component<ComponentListItem.Props>
}
}
private async install(item: ArduinoComponent) {
await this.props.install(item);
}
render(): React.ReactNode {
const { item } = this.props;
@ -27,7 +31,8 @@ export class ComponentListItem extends React.Component<ComponentListItem.Props>
const description = !!item.description && <div className={style.DESCRIPTION_CLASS}>{item.description}</div>;
const moreInfo = !!item.moreInfoLink && <a href={item.moreInfoLink} onClick={this.onClick}>More info</a>;
const install = item.installable && !item.installedVersion && <button className={style.INSTALL_BTN_CLASS}>INSTALL</button>;
const install = this.props.install && item.installable && !item.installedVersion &&
<button className={style.INSTALL_BTN_CLASS} onClick={this.install.bind(this, item)}>INSTALL</button>;
return <div className={[style.LIST_ITEM_CLASS, style.NO_SELECT_CLASS].join(' ')}>
<div className={style.HEADER_CLASS}>
@ -52,6 +57,7 @@ export namespace ComponentListItem {
export interface Props {
readonly item: ArduinoComponent;
readonly windowService: WindowService;
readonly install: (comp: ArduinoComponent) => Promise<void>;
}
export namespace Styles {

View File

@ -7,7 +7,7 @@ export class ComponentList extends React.Component<ComponentList.Props> {
render(): React.ReactNode {
return <div>
{this.props.items.map(item => <ComponentListItem key={item.name} item={item} windowService={this.props.windowService}/>)}
{this.props.items.map(item => <ComponentListItem key={item.name} item={item} windowService={this.props.windowService} install={this.props.install} />)}
</div>;
}
@ -18,6 +18,7 @@ export namespace ComponentList {
export interface Props {
readonly items: ArduinoComponent[];
readonly windowService: WindowService;
readonly install: (comp: ArduinoComponent) => Promise<void>;
}
}

View File

@ -3,6 +3,7 @@ import { WindowService } from '@theia/core/lib/browser/window/window-service';
import { ComponentList } from './component-list';
import { SearchBar } from './search-bar';
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
import { InstallationProgressDialog } from '../installation-progress-dialog';
export class FilterableListContainer extends React.Component<FilterableListContainer.Props, FilterableListContainer.State> {
@ -27,6 +28,7 @@ export class FilterableListContainer extends React.Component<FilterableListConta
/>
<ComponentList
items={this.state.items}
install={this.install.bind(this)}
windowService={this.props.windowService}
/>
</div>
@ -42,6 +44,18 @@ export class FilterableListContainer extends React.Component<FilterableListConta
});
}
protected async install(comp: ArduinoComponent): Promise<void> {
const dialog = new InstallationProgressDialog(comp.name);
dialog.open();
try {
await this.props.service.install(comp);
const { items } = await this.props.service.search({ query: this.state.filterText });
this.setState({ items });
} finally {
dialog.close();
}
}
}
export namespace FilterableListContainer {
@ -62,6 +76,7 @@ export namespace FilterableListContainer {
export interface ComponentSource {
search(req: { query: string }): Promise<{ items: ArduinoComponent[] }>
install(board: ArduinoComponent): Promise<void>;
}
}

View File

@ -0,0 +1,12 @@
import { AbstractDialog } from "@theia/core/lib/browser";
export class InstallationProgressDialog extends AbstractDialog<string> {
readonly value: "does-not-matter";
constructor(componentName: string) {
super({ title: 'Installation in progress' });
this.contentNode.textContent = `Installing ${componentName}. Please wait.`;
}
}

View File

@ -5,7 +5,9 @@ export const BoardsService = Symbol('BoardsService');
export interface BoardsService {
connectedBoards(): Promise<{ boards: Board[], current?: Board }>;
search(options: { query?: string }): Promise<{ items: Board[] }>;
install(board: Board): Promise<void>;
}
export interface Board extends ArduinoComponent {
id: string;
}

View File

@ -4,6 +4,7 @@ export const LibraryServicePath = '/services/library-service';
export const LibraryService = Symbol('LibraryService');
export interface LibraryService {
search(options: { query?: string }): Promise<{ items: Library[] }>;
install(board: Library): Promise<void>;
}
export interface Library extends ArduinoComponent {

View File

@ -1,6 +1,6 @@
import { injectable, inject } from 'inversify';
import { BoardsService, Board } from '../common/protocol/boards-service';
import { PlatformSearchReq, PlatformSearchResp } from './cli-protocol/core_pb';
import { PlatformSearchReq, PlatformSearchResp, PlatformInstallReq, PlatformInstallResp, PlatformListReq, PlatformListResp } from './cli-protocol/core_pb';
import { CoreClientProvider } from './core-client-provider';
@injectable()
@ -14,24 +14,76 @@ export class BoardsServiceImpl implements BoardsService {
}
async search(options: { query?: string }): Promise<{ items: Board[] }> {
let items: Board[] = [];
const { client, instance } = await this.coreClientProvider.getClient();
const installedPlatformsReq = new PlatformListReq();
installedPlatformsReq.setInstance(instance);
const installedPlatformsResp = await new Promise<PlatformListResp>((resolve, reject) =>
client.platformList(installedPlatformsReq, (err, resp) => (!!err ? reject : resolve)(!!err ? err : resp))
);
const installedPlatforms = installedPlatformsResp.getInstalledPlatformList();
console.info("Installed platforms", installedPlatforms);
const req = new PlatformSearchReq();
req.setSearchArgs(options.query || "");
req.setInstance(instance);
const resp = await new Promise<PlatformSearchResp>((resolve, reject) => client.platformSearch(req, (err, resp) => (!!err ? reject : resolve)(!!err ? err : resp)));
items = resp.getSearchOutputList().map(o => <Board>{
name: o.getName(),
author: "Someone",
availableVersions: [],
description: "lorem ipsum sit dolor amet",
installable: false,
summary: "has none"
let items = resp.getSearchOutputList().map(o => {
let installedVersion: string | undefined;
const matchingPlatform = installedPlatforms.find(ip => ip.getId().startsWith(`${o.getId()}@`));
if (!!matchingPlatform) {
installedVersion = matchingPlatform.getInstalled();
}
const result: Board = {
id: o.getId(),
name: o.getName(),
author: "Someone",
availableVersions: [ o.getVersion() ],
description: "lorem ipsum sit dolor amet",
installable: true,
summary: "has none",
installedVersion,
}
return result;
}).sort((a, b) => {
if (a.name < b.name) {
return -1;
} else if (a.name === b.name) {
return 0;
} else {
return 1;
}
});
return { items };
}
async install(board: Board): Promise<void> {
const { client, instance } = await this.coreClientProvider.getClient();
const [ platform, boardName ] = board.id.split(":");
const req = new PlatformInstallReq();
req.setArchitecture(boardName);
req.setPlatformPackage(platform);
req.setVersion(board.availableVersions[0]);
req.setInstance(instance);
console.info("Starting board installation", board);
const resp = client.platformInstall(req);
resp.on('data', (r: PlatformInstallResp) => {
const prog = r.getProgress();
if (prog) {
console.info(`downloading ${prog.getFile()}: ${prog.getCompleted()}%`)
}
});
await new Promise<void>((resolve, reject) => {
resp.on('end', resolve);
resp.on('error', reject);
});
console.info("Board installation done", board);
}
}

View File

@ -6,6 +6,8 @@ import { WorkspaceServiceExt } from '../browser/workspace-service-ext';
import { FileSystem } from '@theia/filesystem/lib/common';
import URI from '@theia/core/lib/common/uri';
import { CoreClientProvider, Client } from './core-client-provider';
import * as fs from 'fs';
import * as path from 'path';
@injectable()
export class CoreClientProviderImpl implements CoreClientProvider {
@ -44,14 +46,15 @@ export class CoreClientProviderImpl implements CoreClientProvider {
console.info(` >>> Creating and caching a new client for ${rootUri}...`);
const client = new ArduinoCoreClient('localhost:50051', grpc.credentials.createInsecure());
const config = new Configuration();
const path = await this.fileSystem.getFsPath(rootUri);
if (!path) {
const rootPath = await this.fileSystem.getFsPath(rootUri);
if (!rootPath) {
throw new Error(`Could not resolve file-system path of URI: ${rootUri}.`);
}
config.setSketchbookdir(path);
config.setDatadir(path);
config.setDownloadsdir(path);
config.setSketchbookdir(rootPath);
config.setDatadir(rootPath);
config.setDownloadsdir(rootPath);
const initReq = new InitReq();
initReq.setConfiguration(config);
@ -60,19 +63,27 @@ export class CoreClientProviderImpl implements CoreClientProvider {
if (!instance) {
throw new Error(`Could not retrieve instance from the initialize response.`);
}
const updateReq = new UpdateIndexReq();
updateReq.setInstance(instance);
const updateResp = client.updateIndex(updateReq);
updateResp.on('data', (o: UpdateIndexResp) => {
const progress = o.getDownloadProgress();
if (progress) {
if (progress.getCompleted()) {
console.log(`Download${progress.getFile() ? ` of ${progress.getFile()}` : ''} completed.`);
} else {
console.log(`Downloading${progress.getFile() ? ` ${progress.getFile()}:` : ''} ${progress.getDownloaded()}.`);
// workaround to speed up startup on existing workspaces
if (!fs.existsSync(path.join(config.getDatadir(), "package_index.json"))) {
const updateReq = new UpdateIndexReq();
updateReq.setInstance(instance);
const updateResp = client.updateIndex(updateReq);
updateResp.on('data', (o: UpdateIndexResp) => {
const progress = o.getDownloadProgress();
if (progress) {
if (progress.getCompleted()) {
console.log(`Download${progress.getFile() ? ` of ${progress.getFile()}` : ''} completed.`);
} else {
console.log(`Downloading${progress.getFile() ? ` ${progress.getFile()}:` : ''} ${progress.getDownloaded()}.`);
}
}
}
});
});
await new Promise<void>((resolve, reject) => {
updateResp.on('error', reject);
updateResp.on('end', resolve);
});
}
// TODO: revisit this!!!
// `updateResp.on('data'` is called only when running, for instance, `compile`. It does not run eagerly.
// await new Promise<void>((resolve, reject) => {

View File

@ -24,8 +24,9 @@ export class CoreServiceImpl implements CoreService {
const { uri } = options;
const sketchpath = await this.fileSystem.getFsPath(options.uri);
if (!sketchpath) {
throw new Error(`Cannot resolve FS path for URI: ${uri}.`);
throw new Error(`Cannot resolve filesystem path for URI: ${uri}.`);
}
const { client, instance } = await this.coreClientProvider.getClient(uri);
// const boards = await this.boardsService.connectedBoards();
// if (!boards.current) {
@ -33,15 +34,6 @@ export class CoreServiceImpl implements CoreService {
// }
// https://github.com/cmaglie/arduino-cli/blob/bd5e78701e7546787649d3cca6b21c5d22d0e438/cli/compile/compile.go#L78-L88
const installPlatformReq = new PlatformInstallReq();
installPlatformReq.setArchitecture('samd');
installPlatformReq.setVersion('1.6.0');
installPlatformReq.setInstance(instance);
const resp = client.platformInstall(installPlatformReq);
console.log(resp);
const installLibReq = new LibraryInstallReq();
installLibReq.setInstance(instance);
installLibReq.setName('arduino:samd');

View File

@ -43,4 +43,8 @@ export class LibraryServiceImpl implements LibraryService {
};
}
async install(board: Library): Promise<void> {
}
}