diff --git a/.vscode/tasks.json b/.vscode/tasks.json index d9a87c6fd..c2a13f3ee 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -35,7 +35,7 @@ "panel": "new", "clear": false } - } + }, { "label": "Arduino IDE - Watch Browser App", "type": "shell", diff --git a/arduino-ide-extension/package.json b/arduino-ide-extension/package.json index 7fc579cff..60197b311 100644 --- a/arduino-ide-extension/package.json +++ b/arduino-ide-extension/package.json @@ -28,8 +28,8 @@ "@theia/monaco": "next", "@theia/navigator": "next", "@theia/outline-view": "next", - "@theia/preferences": "next", "@theia/output": "next", + "@theia/preferences": "next", "@theia/search-in-workspace": "next", "@theia/terminal": "next", "@theia/workspace": "next", @@ -48,14 +48,15 @@ "@types/which": "^1.3.1", "ajv": "^6.5.3", "async-mutex": "^0.3.0", + "axios": "^0.21.1", "css-element-queries": "^1.2.0", "dateformat": "^3.0.3", "deepmerge": "^4.2.2", "fuzzy": "^0.1.3", "glob": "^7.1.6", "google-protobuf": "^3.11.4", - "lodash.debounce": "^4.0.8", "js-yaml": "^3.13.1", + "lodash.debounce": "^4.0.8", "ncp": "^2.0.0", "p-queue": "^5.0.0", "ps-tree": "^1.2.0", diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts index ba580d8b7..4b60d56c7 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -217,6 +217,8 @@ import { NotificationManager } from './theia/messages/notifications-manager'; import { NotificationManager as TheiaNotificationManager } from '@theia/messages/lib/browser/notifications-manager'; import { NotificationsRenderer as TheiaNotificationsRenderer } from '@theia/messages/lib/browser/notifications-renderer'; import { NotificationsRenderer } from './theia/messages/notifications-renderer'; +import { NewVersionNotification } from './contributions/new-version-notification' +import { UpdatesRetriever } from './updates/updates-retriever'; const ElementQueries = require('css-element-queries/src/ElementQueries'); @@ -653,4 +655,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { rebind(TheiaNotificationManager).toService(NotificationManager); bind(NotificationsRenderer).toSelf().inSingletonScope(); rebind(TheiaNotificationsRenderer).toService(NotificationsRenderer); + + bind(NewVersionNotification).toSelf().inSingletonScope(); + bind(FrontendApplicationContribution).toService(NewVersionNotification); + + bind(UpdatesRetriever).toSelf().inSingletonScope(); }); diff --git a/arduino-ide-extension/src/browser/boards/boards-data-menu-updater.ts b/arduino-ide-extension/src/browser/boards/boards-data-menu-updater.ts index a7beed9bb..b911fd2a0 100644 --- a/arduino-ide-extension/src/browser/boards/boards-data-menu-updater.ts +++ b/arduino-ide-extension/src/browser/boards/boards-data-menu-updater.ts @@ -13,7 +13,7 @@ import { BoardsDataStore } from './boards-data-store'; import { MainMenuManager } from '../../common/main-menu-manager'; import { ArduinoMenus, unregisterSubmenu } from '../menu/arduino-menus'; -@injectable() +@injectable() export class BoardsDataMenuUpdater implements FrontendApplicationContribution { @inject(CommandRegistry) protected readonly commandRegistry: CommandRegistry; diff --git a/arduino-ide-extension/src/browser/contributions/new-version-notification.ts b/arduino-ide-extension/src/browser/contributions/new-version-notification.ts new file mode 100644 index 000000000..b55a7b8ab --- /dev/null +++ b/arduino-ide-extension/src/browser/contributions/new-version-notification.ts @@ -0,0 +1,29 @@ +import { injectable, inject } from 'inversify'; +import { MessageService } from '@theia/core/lib/common/message-service'; +import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application'; +import { UpdatesRetriever } from '../updates/updates-retriever'; +import { shell } from 'electron'; + +const GO_TO_DOWNLOAD_PAGE = 'Go to download page...'; +/** + * 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 NewVersionNotification implements FrontendApplicationContribution { + @inject(UpdatesRetriever) + private readonly updatesRetriever: UpdatesRetriever; + + @inject(MessageService) + protected readonly messageService: MessageService; + + async onStart(): Promise { + if (await this.updatesRetriever.isUpdateAvailable()) { + this.messageService.info('New Arduino IDE version available.', GO_TO_DOWNLOAD_PAGE).then(async answer => { + if (answer === GO_TO_DOWNLOAD_PAGE) { + shell.openExternal('https://www.arduino.cc/en/software#experimental-software'); + } + }) + } + } +} diff --git a/arduino-ide-extension/src/browser/updates/updates-retriever.ts b/arduino-ide-extension/src/browser/updates/updates-retriever.ts new file mode 100644 index 000000000..6537ef718 --- /dev/null +++ b/arduino-ide-extension/src/browser/updates/updates-retriever.ts @@ -0,0 +1,37 @@ +import axios from 'axios'; +import { injectable } from 'inversify'; +import * as os from 'os'; +import { compare } from 'semver'; +import { remote } from 'electron'; + +const BASE_URL = 'https://downloads.arduino.cc'; +const LATEST_LOCATION_PARTIAL_URL = `${BASE_URL}/latest-location/arduino-ide/arduino-ide_latest_`; +const LATEST_VERSION_PARTIAL_URL = `${BASE_URL}/arduino-ide/arduino-ide_`; + +@injectable() +export class UpdatesRetriever { + public async isUpdateAvailable(): Promise { + let filename; + switch (os.type()) { + case 'Linux': + filename = 'Linux_64bit.zip'; + break; + case 'Darwin': + filename = 'macOS_64bit.dmg'; + break; + case 'Windows_NT': + filename = 'Windows_64bit.exe'; + break; + default: + return false; + } + const response = await axios.head(`${LATEST_LOCATION_PARTIAL_URL}${filename}`) + const location = (response.headers?.location as String); + if (location && location.startsWith(LATEST_VERSION_PARTIAL_URL)) { + const latestVersion = location.split('_')[1]; + const version = await remote.app.getVersion(); + return compare(latestVersion, version) === 1; + } + return false; + } +}