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, BoardsPackage, Board } from '../../common/protocol/boards-service'; import { BoardsServiceProvider } from './boards-service-provider'; import { BoardsListWidgetFrontendContribution } from './boards-widget-frontend-contribution'; import { BoardsConfig } from './boards-config'; import { Installable } from '../../common/protocol'; import { ResponseServiceImpl } from '../response-service-impl'; /** * Listens on `BoardsConfig.Config` changes, if a board is selected which does not * have the corresponding core installed, it proposes the user to install the core. */ @injectable() export class BoardsAutoInstaller implements FrontendApplicationContribution { @inject(MessageService) protected readonly messageService: MessageService; @inject(BoardsService) protected readonly boardsService: BoardsService; @inject(BoardsServiceProvider) protected readonly boardsServiceClient: BoardsServiceProvider; @inject(ResponseServiceImpl) protected readonly responseService: ResponseServiceImpl; @inject(BoardsListWidgetFrontendContribution) protected readonly boardsManagerFrontendContribution: BoardsListWidgetFrontendContribution; // Workaround for https://github.com/eclipse-theia/theia/issues/9349 protected notifications: Board[] = []; onStart(): void { this.boardsServiceClient.onBoardsConfigChanged(this.ensureCoreExists.bind(this)); this.ensureCoreExists(this.boardsServiceClient.boardsConfig); } protected ensureCoreExists(config: BoardsConfig.Config): void { const { selectedBoard } = config; if (selectedBoard && !this.notifications.find(board => Board.sameAs(board, selectedBoard))) { this.notifications.push(selectedBoard); this.boardsService.search({}).then(packages => { // filter packagesForBoard selecting matches from the cli (installed packages) // and matches based on the board name // NOTE: this ensures the Deprecated & new packages are all in the array // so that we can check if any of the valid packages is already installed const packagesForBoard = packages.filter(pkg => BoardsPackage.contains(selectedBoard, pkg) || pkg.boards.some(board => board.name === selectedBoard.name)); // check if one of the packages for the board is already installed. if so, no hint if (packagesForBoard.some(({ installedVersion }) => !!installedVersion)) { return; } // filter the installable (not installed) packages, // CLI returns the packages already sorted with the deprecated ones at the end of the list // in order to ensure the new ones are preferred const candidates = packagesForBoard .filter(({ installable, installedVersion }) => installable && !installedVersion); const candidate = candidates[0]; if (candidate) { const version = candidate.availableVersions[0] ? `[v ${candidate.availableVersions[0]}]` : ''; // tslint:disable-next-line:max-line-length this.messageService.info(`The \`"${candidate.name} ${version}"\` core has to be installed for the currently selected \`"${selectedBoard.name}"\` board. Do you want to install it now?`, 'Install Manually', 'Yes').then(async answer => { const index = this.notifications.findIndex(board => Board.sameAs(board, selectedBoard)); if (index !== -1) { this.notifications.splice(index, 1); } if (answer === 'Yes') { await Installable.installWithProgress({ installable: this.boardsService, item: candidate, messageService: this.messageService, responseService: this.responseService, version: candidate.availableVersions[0] }); return } if (answer) { this.boardsManagerFrontendContribution.openView({ reveal: true }).then(widget => widget.refresh(candidate.name.toLocaleLowerCase())); } }); } }) } } }