import type { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution'; import { nls } from '@theia/core/lib/common/nls'; import { inject, injectable } from '@theia/core/shared/inversify'; import { InstallManually, Later } from '../../common/nls'; import { ArduinoComponent, BoardsPackage, BoardsService, LibraryPackage, LibraryService, ResponseServiceClient, Searchable, } from '../../common/protocol'; import { Installable } from '../../common/protocol/installable'; import { ExecuteWithProgress } from '../../common/protocol/progressible'; import { BoardsListWidgetFrontendContribution } from '../boards/boards-widget-frontend-contribution'; import { LibraryListWidgetFrontendContribution } from '../library/library-widget-frontend-contribution'; import { WindowServiceExt } from '../theia/core/window-service-ext'; import type { ListWidget } from '../widgets/component-list/list-widget'; import { Command, CommandRegistry, Contribution } from './contribution'; const NoUpdates = nls.localize( 'arduino/checkForUpdates/noUpdates', 'There are no recent updates available.' ); const PromptUpdateBoards = nls.localize( 'arduino/checkForUpdates/promptUpdateBoards', 'Updates are available for some of your boards.' ); const PromptUpdateLibraries = nls.localize( 'arduino/checkForUpdates/promptUpdateLibraries', 'Updates are available for some of your libraries.' ); const UpdatingBoards = nls.localize( 'arduino/checkForUpdates/updatingBoards', 'Updating boards...' ); const UpdatingLibraries = nls.localize( 'arduino/checkForUpdates/updatingLibraries', 'Updating libraries...' ); const InstallAll = nls.localize( 'arduino/checkForUpdates/installAll', 'Install All' ); interface Task { readonly run: () => Promise; readonly item: T; } const Updatable = { type: 'Updatable' } as const; @injectable() export class CheckForUpdates extends Contribution { @inject(WindowServiceExt) private readonly windowService: WindowServiceExt; @inject(ResponseServiceClient) private readonly responseService: ResponseServiceClient; @inject(BoardsService) private readonly boardsService: BoardsService; @inject(LibraryService) private readonly libraryService: LibraryService; @inject(BoardsListWidgetFrontendContribution) private readonly boardsContribution: BoardsListWidgetFrontendContribution; @inject(LibraryListWidgetFrontendContribution) private readonly librariesContribution: LibraryListWidgetFrontendContribution; override registerCommands(register: CommandRegistry): void { register.registerCommand(CheckForUpdates.Commands.CHECK_FOR_UPDATES, { execute: () => this.checkForUpdates(false), }); } override async onReady(): Promise { const checkForUpdates = this.preferences['arduino.checkForUpdates']; if (checkForUpdates) { this.windowService.isFirstWindow().then((firstWindow) => { if (firstWindow) { this.checkForUpdates(); } }); } } private async checkForUpdates(silent = true) { const [boardsPackages, libraryPackages] = await Promise.all([ this.boardsService.search(Updatable), this.libraryService.search(Updatable), ]); this.promptUpdateBoards(boardsPackages); this.promptUpdateLibraries(libraryPackages); if (!libraryPackages.length && !boardsPackages.length && !silent) { this.messageService.info(NoUpdates); } } private promptUpdateBoards(items: BoardsPackage[]): void { this.prompt({ items, installable: this.boardsService, viewContribution: this.boardsContribution, viewSearchOptions: { query: '', ...Updatable }, promptMessage: PromptUpdateBoards, updatingMessage: UpdatingBoards, }); } private promptUpdateLibraries(items: LibraryPackage[]): void { this.prompt({ items, installable: this.libraryService, viewContribution: this.librariesContribution, viewSearchOptions: { query: '', topic: 'All', ...Updatable }, promptMessage: PromptUpdateLibraries, updatingMessage: UpdatingLibraries, }); } private prompt< T extends ArduinoComponent, S extends Searchable.Options >(options: { items: T[]; installable: Installable; viewContribution: AbstractViewContribution>; viewSearchOptions: S; promptMessage: string; updatingMessage: string; }): void { const { items, installable, viewContribution, promptMessage: message, viewSearchOptions, updatingMessage, } = options; if (!items.length) { return; } this.messageService .info(message, Later, InstallManually, InstallAll) .then((answer) => { if (answer === InstallAll) { const tasks = items.map((item) => this.createInstallTask(item, installable) ); this.executeTasks(updatingMessage, tasks); } else if (answer === InstallManually) { viewContribution .openView({ reveal: true }) .then((widget) => widget.refresh(viewSearchOptions)); } }); } private async executeTasks( message: string, tasks: Task[] ): Promise { if (tasks.length) { return ExecuteWithProgress.withProgress( message, this.messageService, async (progress) => { try { const total = tasks.length; let count = 0; for (const { run, item } of tasks) { try { await run(); // runs update sequentially. // TODO: is parallel update desired? } catch (err) { console.error(err); this.messageService.error( `Failed to update ${item.name}. ${err}` ); } finally { progress.report({ work: { total, done: ++count } }); } } } finally { progress.cancel(); } } ); } } private createInstallTask( item: T, installable: Installable ): Task { const latestVersion = item.availableVersions[0]; return { item, run: () => Installable.installWithProgress({ installable, item, version: latestVersion, messageService: this.messageService, responseService: this.responseService, keepOutput: true, }), }; } } export namespace CheckForUpdates { export namespace Commands { export const CHECK_FOR_UPDATES: Command = Command.toLocalizedCommand( { id: 'arduino-check-for-updates', label: 'Check for Arduino Updates', category: 'Arduino', }, 'arduino/checkForUpdates/checkForUpdates' ); } }