Files
mattermostest/src/main/autoUpdater.ts
Tasos Boulis 167f7d832a [MM-47754] Remove update from downloads when "Restart and Upgrade" is clicked (#2299)
* Remove update from downloads when "Restart and Upgrade" is clicked

* Fix failing test

* Mock downloadsManager in autoUpdater tests
2022-10-21 13:27:24 +03:00

189 lines
6.5 KiB
TypeScript

// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import path from 'path';
import {dialog, ipcMain, app, nativeImage} from 'electron';
import log from 'electron-log';
import {autoUpdater, CancellationToken, ProgressInfo, UpdateInfo} from 'electron-updater';
import downloadsManager from 'main/downloadsManager';
import {localizeMessage} from 'main/i18nManager';
import {displayUpgrade, displayRestartToUpgrade} from 'main/notifications';
import {
CANCEL_UPGRADE,
UPDATE_AVAILABLE,
UPDATE_DOWNLOADED,
CHECK_FOR_UPDATES,
UPDATE_SHORTCUT_MENU,
UPDATE_PROGRESS,
NO_UPDATE_AVAILABLE,
CANCEL_UPDATE_DOWNLOAD,
UPDATE_REMIND_LATER,
} from 'common/communication';
import Config from 'common/config';
const NEXT_NOTIFY = 86400000; // 24 hours
const NEXT_CHECK = 3600000; // 1 hour
log.transports.file.level = 'info';
autoUpdater.logger = log;
autoUpdater.autoDownload = false;
autoUpdater.disableWebInstaller = true;
const assetsDir = path.resolve(app.getAppPath(), 'assets');
const appIconURL = path.resolve(assetsDir, 'appicon_with_spacing_32.png');
const appIcon = nativeImage.createFromPath(appIconURL);
/** to test this during development
* add the following to electron-builder.json in the publish entry
{
"provider": "generic",
"url": "http://localhost:8000"
},
* create a packaged build, copy that to a directory B (I usually do a third C copy to be able to go back without packaging again)
* upgrade the package.json version
* package a second copy of the app
* on release dir setup an http server (using `python -m SimpleHTTPServer` should match the above entry)
* start the app from directory B
* if the app upgraded and you want to repeat, simply copy C into B if you did the C step, if not, package again.
* yeah, it is a time consuming process :( improve this doc if you find a way to go faster.
**/
export class UpdateManager {
cancellationToken?: CancellationToken;
lastNotification?: NodeJS.Timeout;
lastCheck?: NodeJS.Timeout;
versionAvailable?: string;
versionDownloaded?: string;
constructor() {
this.cancellationToken = new CancellationToken();
autoUpdater.on('error', (err: Error) => {
log.error(`[Mattermost] There was an error while trying to update: ${err}`);
});
autoUpdater.on('update-available', (info: UpdateInfo) => {
autoUpdater.removeListener('update-not-available', this.displayNoUpgrade);
this.versionAvailable = info.version;
ipcMain.emit(UPDATE_SHORTCUT_MENU);
log.info(`[Mattermost] available version ${info.version}`);
this.notify();
});
autoUpdater.on('update-downloaded', (info: UpdateInfo) => {
this.versionDownloaded = info.version;
ipcMain.emit(UPDATE_SHORTCUT_MENU);
log.info(`[Mattermost] downloaded version ${info.version}`);
this.notifyDownloaded();
});
autoUpdater.on('download-progress', (progress: ProgressInfo) => {
ipcMain.emit(UPDATE_PROGRESS, null, progress);
});
ipcMain.on(CANCEL_UPGRADE, () => {
log.info('[Mattermost] User Canceled upgrade');
});
ipcMain.on(CHECK_FOR_UPDATES, () => {
this.checkForUpdates(true);
});
ipcMain.on(CANCEL_UPDATE_DOWNLOAD, this.handleCancelDownload);
ipcMain.on(UPDATE_REMIND_LATER, this.handleRemindLater);
}
notify = (): void => {
if (this.lastNotification) {
clearTimeout(this.lastNotification);
}
this.lastNotification = setTimeout(this.notify, NEXT_NOTIFY);
if (this.versionDownloaded) {
this.notifyDownloaded();
} else if (this.versionAvailable) {
this.notifyUpgrade();
}
}
notifyUpgrade = (): void => {
ipcMain.emit(UPDATE_AVAILABLE, null, this.versionAvailable);
displayUpgrade(this.versionAvailable || 'unknown', this.handleDownload);
}
notifyDownloaded = (): void => {
ipcMain.emit(UPDATE_DOWNLOADED, null, this.versionDownloaded);
displayRestartToUpgrade(this.versionDownloaded || 'unknown', this.handleUpdate);
}
handleDownload = (): void => {
if (this.lastCheck) {
clearTimeout(this.lastCheck);
}
autoUpdater.downloadUpdate(this.cancellationToken);
}
handleCancelDownload = (): void => {
this.cancellationToken?.cancel();
this.cancellationToken = new CancellationToken();
}
handleRemindLater = (): void => {
// TODO
}
handleOnQuit = (): void => {
if (this.versionDownloaded) {
autoUpdater.quitAndInstall(true, false);
}
}
handleUpdate = async (): Promise<void> => {
await downloadsManager.removeUpdateBeforeRestart();
autoUpdater.quitAndInstall();
}
displayNoUpgrade = (): void => {
const version = app.getVersion();
ipcMain.emit(NO_UPDATE_AVAILABLE);
dialog.showMessageBox({
title: app.name,
icon: appIcon,
message: localizeMessage('main.autoUpdater.noUpdate.message', 'You\'re up to date'),
type: 'info',
buttons: [localizeMessage('label.ok', 'OK')],
detail: localizeMessage('main.autoUpdater.noUpdate.detail', 'You are using the latest version of the {appName} Desktop App (version {version}). You\'ll be notified when a new version is available to install.', {appName: app.name, version}),
});
}
checkForUpdates = (manually: boolean): void => {
if (!Config.canUpgrade) {
log.info('auto updates are disabled');
return;
}
if (this.lastCheck) {
clearTimeout(this.lastCheck);
}
if (!this.lastNotification || manually) {
if (manually) {
autoUpdater.once('update-not-available', this.displayNoUpgrade);
}
autoUpdater.checkForUpdates().then((result) => {
if (!result?.updateInfo) {
ipcMain.emit(NO_UPDATE_AVAILABLE);
}
}).catch((reason) => {
ipcMain.emit(NO_UPDATE_AVAILABLE);
log.error(`[Mattermost] Failed to check for updates: ${reason}`);
});
this.lastCheck = setTimeout(() => this.checkForUpdates(false), NEXT_CHECK);
}
}
}
const updateManager = new UpdateManager();
export default updateManager;