Check for IDE update at startup (#797)

* Remove check for updates on startup setting

* Remove useless exported function

* Update template-package.json used to package IDE

* Add function to get channel file during packaging step

* Add updates check

* move ide updater on backend

* configure updater options

* add auto update preferences

* TMP check updates on start and download

* index on check-update-startup: fcb8f6e TMP check updates on start and download

* set version to skip on local storage

* add IDE setting to toggle update check on start-up

* comment out check for updates on startup and auto update settings

* Update Theia to 1.22.1

* updated CI

* download changelog and show it in IDE updater dialog

* remove useless file

* remove useless code

* add i18n to updater dialog

* fix i18n

* refactor UpdateInfo typing

* add macos zip to artifacts

* Simply use `--ignore-engines`

* Use correct --ignore-engines

* Fix semver#valid call

* Use C++17

* updated documentation

* add update channel preference

* update updater url

* updated documentation

* Fix the C++ version

* Build flag for cpp

* add disclaimer with correct node version

* Update `electron-builder`

* Fix `Electron.Menu` issue

* Skip electron rebuild

* Rebuild native dependencies beforehand

* Use resolutions section

* Update template-package.json as well

* move ide-updater to electron application

* refactor ide-updater service

* update yarn.lock

* update i18n

* Revert "Add gRPC user agent (#834)"

This reverts commit 5ab3a747a6.

* fix ide download url

* update latest file in CI

* fix i18n check

Co-authored-by: Silvano Cerza <silvanocerza@gmail.com>
Co-authored-by: Francesco Stasi <f.stasi@me.com>
Co-authored-by: Mark Sujew <msujew@yahoo.de>
This commit is contained in:
Alberto Iannaccone
2022-02-15 18:01:19 +01:00
committed by GitHub
parent 9ecff86bbe
commit f660058c75
30 changed files with 1915 additions and 346 deletions

View File

@@ -0,0 +1,159 @@
import { nls } from '@theia/core/lib/common';
import { shell } from 'electron';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import ReactMarkdown from 'react-markdown';
import { ProgressInfo, UpdateInfo } from '../../../common/protocol/ide-updater';
import ProgressBar from '../../components/ProgressBar';
export type IDEUpdaterComponentProps = {
updateInfo: UpdateInfo;
downloadFinished?: boolean;
downloadStarted?: boolean;
progress?: ProgressInfo;
error?: Error;
onDownload: () => void;
onClose: () => void;
onSkipVersion: () => void;
onCloseAndInstall: () => void;
};
export const IDEUpdaterComponent = ({
updateInfo: { version, releaseNotes },
downloadStarted = false,
downloadFinished = false,
progress,
error,
onDownload,
onClose,
onSkipVersion,
onCloseAndInstall,
}: IDEUpdaterComponentProps): React.ReactElement => {
const changelogDivRef = React.useRef() as React.MutableRefObject<
HTMLDivElement
>;
React.useEffect(() => {
if (!!releaseNotes) {
let changelog: string;
if (typeof releaseNotes === 'string') changelog = releaseNotes;
else
changelog = releaseNotes.reduce((acc, item) => {
return item.note ? (acc += `${item.note}\n\n`) : acc;
}, '');
ReactDOM.render(
<ReactMarkdown
components={{
a: ({ href, children, ...props }) => (
<a onClick={() => href && shell.openExternal(href)} {...props}>
{children}
</a>
),
}}
>
{changelog}
</ReactMarkdown>,
changelogDivRef.current
);
}
}, [releaseNotes]);
const closeButton = (
<button onClick={onClose} type="button" className="theia-button secondary">
{nls.localize('arduino/ide-updater/notNowButton', 'Not now')}
</button>
);
return (
<div className="ide-updater-dialog--content">
{downloadFinished ? (
<div className="ide-updater-dialog--downloaded">
<div>
{nls.localize(
'arduino/ide-updater/versionDownloaded',
'Arduino IDE {0} has been downloaded.',
version
)}
</div>
<div>
{nls.localize(
'arduino/ide-updater/closeToInstallNotice',
'Close the software and install the update on your machine.'
)}
</div>
<div className="buttons-container">
{closeButton}
<button
onClick={onCloseAndInstall}
type="button"
className="theia-button close-and-install"
>
{nls.localize(
'arduino/ide-updater/closeAndInstallButton',
'Close and Install'
)}
</button>
</div>
</div>
) : downloadStarted ? (
<div className="ide-updater-dialog--downloading">
<div>
{nls.localize(
'arduino/ide-updater/downloadingNotice',
'Downloading the latest version of the Arduino IDE.'
)}
</div>
<ProgressBar percent={progress?.percent} showPercentage />
</div>
) : (
<div className="ide-updater-dialog--pre-download">
<div className="ide-updater-dialog--logo-container">
<div className="ide-updater-dialog--logo"></div>
</div>
<div className="ide-updater-dialog--new-version-text dialogSection">
<div className="dialogRow">
<div className="bold">
{nls.localize(
'arduino/ide-updater/updateAvailable',
'Update Available'
)}
</div>
</div>
<div className="dialogRow">
{nls.localize(
'arduino/ide-updater/newVersionAvailable',
'A new version of Arduino IDE ({0}) is available for download.',
version
)}
</div>
{releaseNotes && (
<div className="dialogRow">
<div className="changelog-container" ref={changelogDivRef} />
</div>
)}
<div className="buttons-container">
<button
onClick={onSkipVersion}
type="button"
className="theia-button secondary skip-version"
>
{nls.localize(
'arduino/ide-updater/skipVersionButton',
'Skip Version'
)}
</button>
<div className="push"></div>
{closeButton}
<button
onClick={onDownload}
type="button"
className="theia-button primary"
>
{nls.localize('arduino/ide-updater/downloadButton', 'Download')}
</button>
</div>
</div>
</div>
)}
{!!error && <div className="error-container">{error}</div>}
</div>
);
};

View File

@@ -0,0 +1,166 @@
import * as React from 'react';
import { inject, injectable } from 'inversify';
import { DialogProps } from '@theia/core/lib/browser/dialogs';
import { AbstractDialog } from '../../theia/dialogs/dialogs';
import { Widget } from '@phosphor/widgets';
import { Message } from '@phosphor/messaging';
import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget';
import { nls } from '@theia/core';
import { IDEUpdaterComponent } from './ide-updater-component';
import { IDEUpdaterCommands } from '../../ide-updater/ide-updater-commands';
import {
IDEUpdaterClient,
ProgressInfo,
UpdateInfo,
} from '../../../common/protocol/ide-updater';
import { LocalStorageService } from '@theia/core/lib/browser';
import { SKIP_IDE_VERSION } from '../../arduino-frontend-contribution';
@injectable()
export class IDEUpdaterDialogWidget extends ReactWidget {
protected isOpen = new Object();
updateInfo: UpdateInfo;
progressInfo: ProgressInfo | undefined;
error: Error | undefined;
downloadFinished: boolean;
downloadStarted: boolean;
onClose: () => void;
@inject(IDEUpdaterCommands)
protected readonly updater: IDEUpdaterCommands;
@inject(IDEUpdaterClient)
protected readonly updaterClient: IDEUpdaterClient;
@inject(LocalStorageService)
protected readonly localStorageService: LocalStorageService;
init(updateInfo: UpdateInfo, onClose: () => void): void {
this.updateInfo = updateInfo;
this.progressInfo = undefined;
this.error = undefined;
this.downloadStarted = false;
this.downloadFinished = false;
this.onClose = onClose;
this.updaterClient.onError((e) => {
this.error = e;
this.update();
});
this.updaterClient.onDownloadProgressChanged((e) => {
this.progressInfo = e;
this.update();
});
this.updaterClient.onDownloadFinished((e) => {
this.downloadFinished = true;
this.update();
});
}
async onSkipVersion(): Promise<void> {
this.localStorageService.setData<string>(
SKIP_IDE_VERSION,
this.updateInfo.version
);
this.close();
}
close(): void {
super.close();
this.onClose();
}
onDispose(): void {
if (this.downloadStarted && !this.downloadFinished)
this.updater.stopDownload();
}
async onDownload(): Promise<void> {
this.progressInfo = undefined;
this.downloadStarted = true;
this.error = undefined;
this.updater.downloadUpdate();
this.update();
}
onCloseAndInstall(): void {
this.updater.quitAndInstall();
}
protected render(): React.ReactNode {
return !!this.updateInfo ? (
<form>
<IDEUpdaterComponent
updateInfo={this.updateInfo}
downloadStarted={this.downloadStarted}
downloadFinished={this.downloadFinished}
progress={this.progressInfo}
onClose={this.close.bind(this)}
onSkipVersion={this.onSkipVersion.bind(this)}
onDownload={this.onDownload.bind(this)}
onCloseAndInstall={this.onCloseAndInstall.bind(this)}
/>
</form>
) : null;
}
}
@injectable()
export class IDEUpdaterDialogProps extends DialogProps {}
@injectable()
export class IDEUpdaterDialog extends AbstractDialog<UpdateInfo> {
@inject(IDEUpdaterDialogWidget)
protected readonly widget: IDEUpdaterDialogWidget;
constructor(
@inject(IDEUpdaterDialogProps)
protected readonly props: IDEUpdaterDialogProps
) {
super({
title: nls.localize(
'arduino/updater/ideUpdaterDialog',
'Software Update'
),
});
this.contentNode.classList.add('ide-updater-dialog');
this.acceptButton = undefined;
}
get value(): UpdateInfo {
return this.widget.updateInfo;
}
protected onAfterAttach(msg: Message): void {
if (this.widget.isAttached) {
Widget.detach(this.widget);
}
Widget.attach(this.widget, this.contentNode);
super.onAfterAttach(msg);
this.update();
}
async open(
data: UpdateInfo | undefined = undefined
): Promise<UpdateInfo | undefined> {
if (data && data.version) {
this.widget.init(data, this.close.bind(this));
return super.open();
}
}
protected onUpdateRequest(msg: Message): void {
super.onUpdateRequest(msg);
this.widget.update();
}
protected onActivateRequest(msg: Message): void {
super.onActivateRequest(msg);
this.widget.activate();
}
close(): void {
this.widget.dispose();
super.close();
}
}

View File

@@ -260,18 +260,6 @@ export class SettingsComponent extends React.Component<
'Verify code after upload'
)}
</label>
<label className="flex-line">
<input
type="checkbox"
checked={this.state.checkForUpdates}
onChange={this.checkForUpdatesDidChange}
disabled={true}
/>
{nls.localize(
'arduino/preferences/checkForUpdates',
'Check for updates on startup'
)}
</label>
<label className="flex-line">
<input
type="checkbox"
@@ -444,7 +432,9 @@ export class SettingsComponent extends React.Component<
);
}
protected noopKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
protected noopKeyDown = (
event: React.KeyboardEvent<HTMLInputElement>
): void => {
if (this.isControlKey(event)) {
return;
}
@@ -454,7 +444,7 @@ export class SettingsComponent extends React.Component<
protected numbersOnlyKeyDown = (
event: React.KeyboardEvent<HTMLInputElement>
) => {
): void => {
if (this.isControlKey(event)) {
return;
}
@@ -466,7 +456,7 @@ export class SettingsComponent extends React.Component<
}
};
protected browseSketchbookDidClick = async () => {
protected browseSketchbookDidClick = async (): Promise<void> => {
const uri = await this.props.fileDialogService.showOpenDialog({
title: nls.localize(
'arduino/preferences/newSketchbookLocation',
@@ -483,7 +473,7 @@ export class SettingsComponent extends React.Component<
}
};
protected editAdditionalUrlDidClick = async () => {
protected editAdditionalUrlDidClick = async (): Promise<void> => {
const additionalUrls = await new AdditionalUrlsDialog(
this.state.additionalUrls,
this.props.windowService
@@ -495,7 +485,7 @@ export class SettingsComponent extends React.Component<
protected editorFontSizeDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
const { value } = event.target;
if (value) {
this.setState({ editorFontSize: parseInt(value, 10) });
@@ -504,7 +494,7 @@ export class SettingsComponent extends React.Component<
protected additionalUrlsDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
this.setState({
additionalUrls: event.target.value.split(',').map((url) => url.trim()),
});
@@ -512,13 +502,13 @@ export class SettingsComponent extends React.Component<
protected autoScaleInterfaceDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
this.setState({ autoScaleInterface: event.target.checked });
};
protected interfaceScaleDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
const { value } = event.target;
const percentage = parseInt(value, 10);
if (isNaN(percentage)) {
@@ -532,31 +522,25 @@ export class SettingsComponent extends React.Component<
protected verifyAfterUploadDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
this.setState({ verifyAfterUpload: event.target.checked });
};
protected checkForUpdatesDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
this.setState({ checkForUpdates: event.target.checked });
};
protected sketchbookShowAllFilesDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
this.setState({ sketchbookShowAllFiles: event.target.checked });
};
protected autoSaveDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
this.setState({ autoSave: event.target.checked ? 'on' : 'off' });
};
protected quickSuggestionsOtherDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
// need to persist react events through lifecycle https://reactjs.org/docs/events.html#event-pooling
const newVal = event.target.checked ? true : false;
@@ -570,7 +554,9 @@ export class SettingsComponent extends React.Component<
});
};
protected themeDidChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
protected themeDidChange = (
event: React.ChangeEvent<HTMLSelectElement>
): void => {
const { selectedIndex } = event.target.options;
const theme = ThemeService.get().getThemes()[selectedIndex];
if (theme) {
@@ -580,14 +566,14 @@ export class SettingsComponent extends React.Component<
protected languageDidChange = (
event: React.ChangeEvent<HTMLSelectElement>
) => {
): void => {
const selectedLanguage = event.target.value;
this.setState({ currentLanguage: selectedLanguage });
};
protected compilerWarningsDidChange = (
event: React.ChangeEvent<HTMLSelectElement>
) => {
): void => {
const { selectedIndex } = event.target.options;
const compilerWarnings = CompilerWarningLiterals[selectedIndex];
if (compilerWarnings) {
@@ -597,26 +583,28 @@ export class SettingsComponent extends React.Component<
protected verboseOnCompileDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
this.setState({ verboseOnCompile: event.target.checked });
};
protected verboseOnUploadDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
this.setState({ verboseOnUpload: event.target.checked });
};
protected sketchpathDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
const sketchbookPath = event.target.value;
if (sketchbookPath) {
this.setState({ sketchbookPath });
}
};
protected noProxyDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
protected noProxyDidChange = (
event: React.ChangeEvent<HTMLInputElement>
): void => {
if (event.target.checked) {
this.setState({ network: 'none' });
} else {
@@ -626,7 +614,7 @@ export class SettingsComponent extends React.Component<
protected manualProxyDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
if (event.target.checked) {
this.setState({ network: Network.Default() });
} else {
@@ -636,7 +624,7 @@ export class SettingsComponent extends React.Component<
protected httpProtocolDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
if (this.state.network !== 'none') {
const network = this.cloneProxySettings;
network.protocol = event.target.checked ? 'http' : 'socks';
@@ -646,7 +634,7 @@ export class SettingsComponent extends React.Component<
protected socksProtocolDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
if (this.state.network !== 'none') {
const network = this.cloneProxySettings;
network.protocol = event.target.checked ? 'socks' : 'http';
@@ -656,7 +644,7 @@ export class SettingsComponent extends React.Component<
protected hostnameDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
if (this.state.network !== 'none') {
const network = this.cloneProxySettings;
network.hostname = event.target.value;
@@ -664,7 +652,9 @@ export class SettingsComponent extends React.Component<
}
};
protected portDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
protected portDidChange = (
event: React.ChangeEvent<HTMLInputElement>
): void => {
if (this.state.network !== 'none') {
const network = this.cloneProxySettings;
network.port = event.target.value;
@@ -674,7 +664,7 @@ export class SettingsComponent extends React.Component<
protected usernameDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
if (this.state.network !== 'none') {
const network = this.cloneProxySettings;
network.username = event.target.value;
@@ -684,7 +674,7 @@ export class SettingsComponent extends React.Component<
protected passwordDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
if (this.state.network !== 'none') {
const network = this.cloneProxySettings;
network.password = event.target.value;

View File

@@ -18,24 +18,22 @@ import {
import { nls } from '@theia/core/lib/common';
import { AsyncLocalizationProvider } from '@theia/core/lib/common/i18n/localization';
const EDITOR_SETTING = 'editor';
const FONT_SIZE_SETTING = `${EDITOR_SETTING}.fontSize`;
const AUTO_SAVE_SETTING = `${EDITOR_SETTING}.autoSave`;
const QUICK_SUGGESTIONS_SETTING = `${EDITOR_SETTING}.quickSuggestions`;
const ARDUINO_SETTING = 'arduino';
const WINDOW_SETTING = `${ARDUINO_SETTING}.window`;
// const IDE_SETTING = `${ARDUINO_SETTING}.ide`;
const COMPILE_SETTING = `${ARDUINO_SETTING}.compile`;
const UPLOAD_SETTING = `${ARDUINO_SETTING}.upload`;
const SKETCHBOOK_SETTING = `${ARDUINO_SETTING}.sketchbook`;
const AUTO_SCALE_SETTING = `${WINDOW_SETTING}.autoScale`;
const ZOOM_LEVEL_SETTING = `${WINDOW_SETTING}.zoomLevel`;
// const AUTO_UPDATE_SETTING = `${IDE_SETTING}.autoUpdate`;
const COMPILE_VERBOSE_SETTING = `${COMPILE_SETTING}.verbose`;
const COMPILE_WARNINGS_SETTING = `${COMPILE_SETTING}.warnings`;
const UPLOAD_VERBOSE_SETTING = `${UPLOAD_SETTING}.verbose`;
const UPLOAD_VERIFY_SETTING = `${UPLOAD_SETTING}.verify`;
const SHOW_ALL_FILES_SETTING = `${SKETCHBOOK_SETTING}.showAllFiles`;
export const EDITOR_SETTING = 'editor';
export const FONT_SIZE_SETTING = `${EDITOR_SETTING}.fontSize`;
export const AUTO_SAVE_SETTING = `${EDITOR_SETTING}.autoSave`;
export const QUICK_SUGGESTIONS_SETTING = `${EDITOR_SETTING}.quickSuggestions`;
export const ARDUINO_SETTING = 'arduino';
export const WINDOW_SETTING = `${ARDUINO_SETTING}.window`;
export const COMPILE_SETTING = `${ARDUINO_SETTING}.compile`;
export const UPLOAD_SETTING = `${ARDUINO_SETTING}.upload`;
export const SKETCHBOOK_SETTING = `${ARDUINO_SETTING}.sketchbook`;
export const AUTO_SCALE_SETTING = `${WINDOW_SETTING}.autoScale`;
export const ZOOM_LEVEL_SETTING = `${WINDOW_SETTING}.zoomLevel`;
export const COMPILE_VERBOSE_SETTING = `${COMPILE_SETTING}.verbose`;
export const COMPILE_WARNINGS_SETTING = `${COMPILE_SETTING}.warnings`;
export const UPLOAD_VERBOSE_SETTING = `${UPLOAD_SETTING}.verbose`;
export const UPLOAD_VERIFY_SETTING = `${UPLOAD_SETTING}.verify`;
export const SHOW_ALL_FILES_SETTING = `${SKETCHBOOK_SETTING}.showAllFiles`;
export interface Settings extends Index {
editorFontSize: number; // `editor.fontSize`
@@ -48,7 +46,6 @@ export interface Settings extends Index {
autoScaleInterface: boolean; // `arduino.window.autoScale`
interfaceScale: number; // `arduino.window.zoomLevel` https://github.com/eclipse-theia/theia/issues/8751
checkForUpdates?: boolean; // `arduino.ide.autoUpdate`
verboseOnCompile: boolean; // `arduino.compile.verbose`
compilerWarnings: CompilerWarnings; // `arduino.compile.warnings`
verboseOnUpload: boolean; // `arduino.upload.verbose`
@@ -93,7 +90,6 @@ export class SettingsService {
@postConstruct()
protected async init(): Promise<void> {
await this.appStateService.reachedState('ready'); // Hack for https://github.com/eclipse-theia/theia/issues/8993
const settings = await this.loadSettings();
this._settings = deepClone(settings);
this.ready.resolve();
@@ -110,7 +106,6 @@ export class SettingsService {
quickSuggestions,
autoScaleInterface,
interfaceScale,
// checkForUpdates,
verboseOnCompile,
compilerWarnings,
verboseOnUpload,
@@ -135,7 +130,6 @@ export class SettingsService {
}),
this.preferenceService.get<boolean>(AUTO_SCALE_SETTING, true),
this.preferenceService.get<number>(ZOOM_LEVEL_SETTING, 0),
// this.preferenceService.get<string>(AUTO_UPDATE_SETTING, true),
this.preferenceService.get<boolean>(COMPILE_VERBOSE_SETTING, true),
this.preferenceService.get<any>(COMPILE_WARNINGS_SETTING, 'None'),
this.preferenceService.get<boolean>(UPLOAD_VERBOSE_SETTING, true),
@@ -154,7 +148,6 @@ export class SettingsService {
quickSuggestions,
autoScaleInterface,
interfaceScale,
// checkForUpdates,
verboseOnCompile,
compilerWarnings,
verboseOnUpload,
@@ -234,7 +227,6 @@ export class SettingsService {
quickSuggestions,
autoScaleInterface,
interfaceScale,
// checkForUpdates,
verboseOnCompile,
compilerWarnings,
verboseOnUpload,
@@ -284,7 +276,6 @@ export class SettingsService {
interfaceScale,
PreferenceScope.User
),
// this.preferenceService.set(AUTO_UPDATE_SETTING, checkForUpdates, PreferenceScope.User),
this.preferenceService.set(
COMPILE_VERBOSE_SETTING,
verboseOnCompile,