Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 25 additions & 6 deletions newIDE/app/src/MainFrame/Preferences/PreferencesProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ import {
selectLanguageOrLocale,
} from '../../Utils/Language';
import { type GamesDashboardOrderBy } from '../../GameDashboard/GamesList';
import { CHECK_APP_UPDATES_TIMEOUT } from '../../Utils/GlobalFetchTimeouts';
import {
CHECK_APP_UPDATES_TIMEOUT,
PERIODIC_APP_UPDATES_TIMEOUT,
} from '../../Utils/GlobalFetchTimeouts';
const electron = optionalRequire('electron');
const ipcRenderer = electron ? electron.ipcRenderer : null;

Expand Down Expand Up @@ -157,6 +160,9 @@ const getPreferences = (): PreferencesValues => {
};

export default class PreferencesProvider extends React.Component<Props, State> {
_periodicUpdateCheckTimeout: ?TimeoutID = null;
_periodicUpdateCheckInterval: ?IntervalID = null;

// $FlowFixMe[missing-local-annot]
state = {
values: (getPreferences(): PreferencesValues),
Expand All @@ -171,7 +177,8 @@ export default class PreferencesProvider extends React.Component<Props, State> {
// $FlowFixMe[method-unbinding]
setAutoDownloadUpdates: (this._setAutoDownloadUpdates.bind(this): any),
// $FlowFixMe[method-unbinding]
checkUpdates: (this._checkUpdates.bind(this): any),
checkUpdates: ((forceDownload?: boolean) =>
this._checkUpdates(forceDownload, true): any),
// $FlowFixMe[method-unbinding]
setAutoDisplayChangelog: (this._setAutoDisplayChangelog.bind(this): any),
// $FlowFixMe[method-unbinding]
Expand Down Expand Up @@ -401,7 +408,19 @@ export default class PreferencesProvider extends React.Component<Props, State> {
};

componentDidMount() {
setTimeout(() => this._checkUpdates(), CHECK_APP_UPDATES_TIMEOUT);
this._periodicUpdateCheckTimeout = setTimeout(
() => this._checkUpdates(),
CHECK_APP_UPDATES_TIMEOUT
);
this._periodicUpdateCheckInterval = setInterval(
() => this._checkUpdates(),
PERIODIC_APP_UPDATES_TIMEOUT
);
}

componentWillUnmount() {
clearTimeout(this._periodicUpdateCheckTimeout);
clearInterval(this._periodicUpdateCheckInterval);
}

_setMultipleValues(updates: ProjectSpecificPreferencesValues) {
Expand Down Expand Up @@ -777,17 +796,17 @@ export default class PreferencesProvider extends React.Component<Props, State> {
);
}

_checkUpdates(forceDownload?: boolean) {
_checkUpdates(forceDownload?: boolean, explicit?: boolean) {
// Checking for updates is only done on Electron.
// Note: This could be abstracted away later if other updates mechanisms
// should be supported.
const { disableCheckForUpdates } = this.props;
if (!ipcRenderer || disableCheckForUpdates) return;

if (!!forceDownload || this.state.values.autoDownloadUpdates) {
ipcRenderer.send('updates-check-and-download');
ipcRenderer.send('updates-check-and-download', { explicit: !!explicit });
} else {
ipcRenderer.send('updates-check');
ipcRenderer.send('updates-check', { explicit: !!explicit });
}
}

Expand Down
31 changes: 25 additions & 6 deletions newIDE/app/src/MainFrame/UpdaterTools.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// @flow
// See ElectronEventsBridge, AboutDialog and electron-app/main.js for handling the updates.

import { Trans } from '@lingui/macro';
import { Trans, t } from '@lingui/macro';
import { type I18n } from '@lingui/core';
import React from 'react';

export type ElectronUpdateStatus = {
Expand All @@ -14,22 +15,40 @@ export type ElectronUpdateStatus = {
| 'download-progress'
| 'update-downloaded'
| 'unknown',
info?: {| version?: string |},
};

export const getElectronUpdateNotificationTitle = (
updateStatus: ElectronUpdateStatus
updateStatus: ElectronUpdateStatus,
i18n: I18n
): string => {
if (updateStatus.status === 'update-available')
return 'A new update is available!';
return i18n._(t`A new update is available!`);

return '';
};

export const getElectronUpdateNotificationBody = (
updateStatus: ElectronUpdateStatus
updateStatus: ElectronUpdateStatus,
i18n: I18n,
autoDownloadUpdates: boolean
): string => {
if (updateStatus.status === 'update-available')
return 'It will be downloaded and installed automatically (unless you deactivated this in preferences)';
if (updateStatus.status === 'update-available') {
const version = updateStatus.info && updateStatus.info.version;
if (autoDownloadUpdates) {
return version
? i18n._(
t`Version ${version} is available and will be downloaded and installed automatically.`
)
: i18n._(t`It will be downloaded and installed automatically.`);
} else {
return version
? i18n._(
t`Version ${version} is available. Open About to download and install it.`
)
: i18n._(t`Open About to download and install it.`);
}
}

return '';
};
Expand Down
42 changes: 33 additions & 9 deletions newIDE/app/src/MainFrame/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ import {
import { type ResourceExternalEditor } from '../ResourcesList/ResourceExternalEditor';
import { type JsExtensionsLoader } from '../JsExtensionsLoader';
import EventsFunctionsExtensionsContext from '../EventsFunctionsExtensionsLoader/EventsFunctionsExtensionsContext';
import optionalRequire from '../Utils/OptionalRequire';
import {
getElectronUpdateNotificationTitle,
getElectronUpdateNotificationBody,
Expand Down Expand Up @@ -232,6 +233,8 @@ import StandaloneDialog from './StandAloneDialog';
import { useInGameEditorSettings } from '../EmbeddedGame/InGameEditorSettings';
import { ProjectScopedContainersAccessor } from '../InstructionOrExpression/EventsScope';
import { useAutomatedRegularInGameEditorRestart } from '../EmbeddedGame/UseAutomatedRegularInGameEditorRestart';
const electron = optionalRequire('electron');
const ipcRendererForUpdates = electron ? electron.ipcRenderer : null;

const GD_STARTUP_TIMES = global.GD_STARTUP_TIMES || [];

Expand Down Expand Up @@ -4370,15 +4373,36 @@ const MainFrame = (props: Props): React.MixedElement => {
const setElectronUpdateStatus = (updateStatus: ElectronUpdateStatus) => {
setState(state => ({ ...state, updateStatus }));

// TODO: use i18n to translate title and body in notification.
// Also, find a way to use preferences to know if user deactivated auto-update.
const notificationTitle = getElectronUpdateNotificationTitle(updateStatus);
const notificationBody = getElectronUpdateNotificationBody(updateStatus);
if (notificationTitle) {
const notification = new window.Notification(notificationTitle, {
body: notificationBody,
});
notification.onclick = () => openAboutDialog(true);
if (updateStatus.status === 'update-downloaded') {
// Update is ready: offer a one-click restart instead of a generic notification.
const version =
updateStatus.info && updateStatus.info.version
? ` (${updateStatus.info.version})`
: '';
const restartNotification = new window.Notification(
i18n._(t`GDevelop update ready${version}`),
{ body: i18n._(t`Click to restart and install the update now.`) }
);
restartNotification.onclick = () => {
if (ipcRendererForUpdates)
ipcRendererForUpdates.send('updates-install-and-quit');
};
} else {
const notificationTitle = getElectronUpdateNotificationTitle(
updateStatus,
i18n
);
const notificationBody = getElectronUpdateNotificationBody(
updateStatus,
i18n,
preferences.values.autoDownloadUpdates
);
if (notificationTitle) {
const notification = new window.Notification(notificationTitle, {
body: notificationBody,
});
notification.onclick = () => openAboutDialog(true);
}
}
};

Expand Down
1 change: 1 addition & 0 deletions newIDE/app/src/Utils/GlobalFetchTimeouts.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,4 @@ export const CREDITS_PACKAGES_FETCH_TIMEOUT = 8000;
export const MARKETING_PLANS_FETCH_TIMEOUT = 8000;

export const CHECK_APP_UPDATES_TIMEOUT = 10000;
export const PERIODIC_APP_UPDATES_TIMEOUT = 60 * 60 * 1000; // 1 hour
29 changes: 22 additions & 7 deletions newIDE/electron-app/app/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -581,24 +581,34 @@ app.on('ready', function() {
closeAllConnections(windowId);
});

ipcMain.on('updates-check-and-download', event => {
// Track whether the current update check was triggered explicitly by the user,
// so that errors are only surfaced to the user for manual checks.
let isExplicitUpdateCheck = false;

ipcMain.on('updates-check-and-download', (event, { explicit } = {}) => {
// This will immediately download an update, then install when the
// app quits.
isExplicitUpdateCheck = !!explicit;
log.info('Starting check for updates (with auto-download if any)');
autoUpdater.autoDownload = true;
autoUpdater.checkForUpdatesAndNotify().catch(err => {
log.error('Error checking for updates:', err);
});
});

ipcMain.on('updates-check', event => {
ipcMain.on('updates-check', (event, { explicit } = {}) => {
isExplicitUpdateCheck = !!explicit;
log.info('Starting check for updates (without auto-download)');
autoUpdater.autoDownload = false;
autoUpdater.checkForUpdates().catch(err => {
log.error('Error checking for updates:', err);
});
});

ipcMain.on('updates-install-and-quit', () => {
autoUpdater.quitAndInstall();
});

function sendUpdateStatus(status) {
log.info(status);
mainWindows.forEach(window => {
Expand All @@ -617,6 +627,7 @@ app.on('ready', function() {
sendUpdateStatus({
message: 'Update available.',
status: 'update-available',
info,
});
});
autoUpdater.on('update-not-available', info => {
Expand All @@ -626,11 +637,15 @@ app.on('ready', function() {
});
});
autoUpdater.on('error', err => {
sendUpdateStatus({
message: 'Error in auto-updater. ' + err,
status: 'error',
err,
});
if (isExplicitUpdateCheck) {
sendUpdateStatus({
message: 'Error in auto-updater. ' + err,
status: 'error',
err,
});
} else {
log.error('Background update check failed:', err);
}
});
autoUpdater.on('download-progress', progressObj => {
let logMessage = 'Download speed: ' + progressObj.bytesPerSecond;
Expand Down