Refactor force-update updater mode
This commit is contained in:
parent
51a9f62778
commit
1fd0144579
3 changed files with 63 additions and 33 deletions
|
@ -12,7 +12,7 @@ import { throttle } from 'lodash';
|
||||||
import type { ParserConfiguration } from 'dashdash';
|
import type { ParserConfiguration } from 'dashdash';
|
||||||
import { createParser } from 'dashdash';
|
import { createParser } from 'dashdash';
|
||||||
import { FAILSAFE_SCHEMA, safeLoad } from 'js-yaml';
|
import { FAILSAFE_SCHEMA, safeLoad } from 'js-yaml';
|
||||||
import { gt, lt } from 'semver';
|
import { gt, gte, lt } from 'semver';
|
||||||
import config from 'config';
|
import config from 'config';
|
||||||
import got from 'got';
|
import got from 'got';
|
||||||
import { v4 as getGuid } from 'uuid';
|
import { v4 as getGuid } from 'uuid';
|
||||||
|
@ -20,6 +20,7 @@ import type { BrowserWindow } from 'electron';
|
||||||
import { app, ipcMain } from 'electron';
|
import { app, ipcMain } from 'electron';
|
||||||
|
|
||||||
import * as durations from '../util/durations';
|
import * as durations from '../util/durations';
|
||||||
|
import { missingCaseError } from '../util/missingCaseError';
|
||||||
import { getTempPath, getUpdateCachePath } from '../../app/attachments';
|
import { getTempPath, getUpdateCachePath } from '../../app/attachments';
|
||||||
import { markShouldNotQuit, markShouldQuit } from '../../app/window_state';
|
import { markShouldNotQuit, markShouldQuit } from '../../app/window_state';
|
||||||
import { DialogType } from '../types/Dialogs';
|
import { DialogType } from '../types/Dialogs';
|
||||||
|
@ -83,6 +84,7 @@ enum DownloadMode {
|
||||||
DifferentialOnly = 'DifferentialOnly',
|
DifferentialOnly = 'DifferentialOnly',
|
||||||
FullOnly = 'FullOnly',
|
FullOnly = 'FullOnly',
|
||||||
Automatic = 'Automatic',
|
Automatic = 'Automatic',
|
||||||
|
ForceUpdate = 'ForceUpdate',
|
||||||
}
|
}
|
||||||
|
|
||||||
type DownloadUpdateResultType = Readonly<{
|
type DownloadUpdateResultType = Readonly<{
|
||||||
|
@ -97,6 +99,12 @@ export type UpdaterOptionsType = Readonly<{
|
||||||
canRunSilently: () => boolean;
|
canRunSilently: () => boolean;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
enum CheckType {
|
||||||
|
Normal = 'Normal',
|
||||||
|
AllowSameVersion = 'AllowSameVersion',
|
||||||
|
ForceDownload = 'ForceDownload',
|
||||||
|
}
|
||||||
|
|
||||||
export abstract class Updater {
|
export abstract class Updater {
|
||||||
protected fileName: string | undefined;
|
protected fileName: string | undefined;
|
||||||
|
|
||||||
|
@ -148,7 +156,7 @@ export abstract class Updater {
|
||||||
//
|
//
|
||||||
|
|
||||||
public async force(): Promise<void> {
|
public async force(): Promise<void> {
|
||||||
return this.checkForUpdatesMaybeInstall(true);
|
return this.checkForUpdatesMaybeInstall(CheckType.ForceDownload);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the updater was about to restart the app but the user cancelled it, show dialog
|
// If the updater was about to restart the app but the user cancelled it, show dialog
|
||||||
|
@ -163,7 +171,7 @@ export abstract class Updater {
|
||||||
);
|
);
|
||||||
this.restarting = false;
|
this.restarting = false;
|
||||||
markShouldNotQuit();
|
markShouldNotQuit();
|
||||||
drop(this.force());
|
drop(this.checkForUpdatesMaybeInstall(CheckType.AllowSameVersion));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async start(): Promise<void> {
|
public async start(): Promise<void> {
|
||||||
|
@ -172,7 +180,7 @@ export abstract class Updater {
|
||||||
this.schedulePoll();
|
this.schedulePoll();
|
||||||
|
|
||||||
await this.deletePreviousInstallers();
|
await this.deletePreviousInstallers();
|
||||||
await this.checkForUpdatesMaybeInstall();
|
await this.checkForUpdatesMaybeInstall(CheckType.Normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -184,7 +192,7 @@ export abstract class Updater {
|
||||||
protected abstract installUpdate(
|
protected abstract installUpdate(
|
||||||
updateFilePath: string,
|
updateFilePath: string,
|
||||||
isSilent: boolean
|
isSilent: boolean
|
||||||
): Promise<void>;
|
): Promise<() => Promise<void>>;
|
||||||
|
|
||||||
//
|
//
|
||||||
// Protected methods
|
// Protected methods
|
||||||
|
@ -223,7 +231,7 @@ export abstract class Updater {
|
||||||
this.logger.info('updater/markCannotUpdate: retrying after user action');
|
this.logger.info('updater/markCannotUpdate: retrying after user action');
|
||||||
|
|
||||||
this.markedCannotUpdate = false;
|
this.markedCannotUpdate = false;
|
||||||
await this.checkForUpdatesMaybeInstall();
|
await this.checkForUpdatesMaybeInstall(CheckType.Normal);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,7 +263,7 @@ export abstract class Updater {
|
||||||
private async safePoll(): Promise<void> {
|
private async safePoll(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
this.logger.info('updater/start: polling now');
|
this.logger.info('updater/start: polling now');
|
||||||
await this.checkForUpdatesMaybeInstall();
|
await this.checkForUpdatesMaybeInstall(CheckType.Normal);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(`updater/start: ${Errors.toLogFormat(error)}`);
|
this.logger.error(`updater/start: ${Errors.toLogFormat(error)}`);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -306,8 +314,11 @@ export abstract class Updater {
|
||||||
if (!downloadResult) {
|
if (!downloadResult) {
|
||||||
logger.warn('downloadAndInstall: no update was downloaded');
|
logger.warn('downloadAndInstall: no update was downloaded');
|
||||||
strictAssert(
|
strictAssert(
|
||||||
mode !== DownloadMode.Automatic && mode !== DownloadMode.FullOnly,
|
mode !== DownloadMode.ForceUpdate &&
|
||||||
'Automatic and full mode downloads are guaranteed to happen or error'
|
mode !== DownloadMode.Automatic &&
|
||||||
|
mode !== DownloadMode.FullOnly,
|
||||||
|
'Automatic/full/force update mode downloads are ' +
|
||||||
|
'guaranteed to happen or error'
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -330,14 +341,21 @@ export abstract class Updater {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.installUpdate(
|
const isSilent =
|
||||||
updateFilePath,
|
|
||||||
updateInfo.vendor?.requireUserConfirmation !== 'true' &&
|
updateInfo.vendor?.requireUserConfirmation !== 'true' &&
|
||||||
this.canRunSilently()
|
this.canRunSilently();
|
||||||
);
|
|
||||||
|
const handler = await this.installUpdate(updateFilePath, isSilent);
|
||||||
|
if (isSilent || mode === DownloadMode.ForceUpdate) {
|
||||||
|
await handler();
|
||||||
|
} else {
|
||||||
|
this.setUpdateListener(handler);
|
||||||
|
}
|
||||||
|
|
||||||
const mainWindow = this.getMainWindow();
|
const mainWindow = this.getMainWindow();
|
||||||
if (mainWindow) {
|
if (mode === DownloadMode.ForceUpdate) {
|
||||||
|
logger.info('downloadAndInstall: force update, no dialog...');
|
||||||
|
} else if (mainWindow) {
|
||||||
logger.info('downloadAndInstall: showing update dialog...');
|
logger.info('downloadAndInstall: showing update dialog...');
|
||||||
mainWindow.webContents.send(
|
mainWindow.webContents.send(
|
||||||
'show-update-dialog',
|
'show-update-dialog',
|
||||||
|
@ -362,21 +380,38 @@ export abstract class Updater {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async checkForUpdatesMaybeInstall(force = false): Promise<void> {
|
private async checkForUpdatesMaybeInstall(
|
||||||
|
checkType: CheckType
|
||||||
|
): Promise<void> {
|
||||||
const { logger } = this;
|
const { logger } = this;
|
||||||
|
|
||||||
logger.info('checkForUpdatesMaybeInstall: checking for update...');
|
logger.info('checkForUpdatesMaybeInstall: checking for update...');
|
||||||
const updateInfo = await this.checkForUpdates(force);
|
const updateInfo = await this.checkForUpdates(checkType);
|
||||||
if (!updateInfo) {
|
if (!updateInfo) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { version: newVersion } = updateInfo;
|
const { version: newVersion } = updateInfo;
|
||||||
|
|
||||||
if (!force && this.version && !gt(newVersion, this.version)) {
|
if (checkType === CheckType.ForceDownload) {
|
||||||
|
await this.downloadAndInstall(updateInfo, DownloadMode.ForceUpdate);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (checkType === CheckType.Normal) {
|
||||||
|
// Verify that the downloaded version is greater than downloaded
|
||||||
|
if (this.version && !gt(newVersion, this.version)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (checkType === CheckType.AllowSameVersion) {
|
||||||
|
// Verify that the downloaded version is greater or the same as downloaded
|
||||||
|
if (this.version && !gte(newVersion, this.version)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw missingCaseError(checkType);
|
||||||
|
}
|
||||||
|
|
||||||
const autoDownloadUpdates = await this.getAutoDownloadUpdateSetting();
|
const autoDownloadUpdates = await this.getAutoDownloadUpdateSetting();
|
||||||
if (autoDownloadUpdates) {
|
if (autoDownloadUpdates) {
|
||||||
await this.downloadAndInstall(updateInfo, DownloadMode.Automatic);
|
await this.downloadAndInstall(updateInfo, DownloadMode.Automatic);
|
||||||
|
@ -443,7 +478,7 @@ export abstract class Updater {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async checkForUpdates(
|
private async checkForUpdates(
|
||||||
forceUpdate = false
|
checkType: CheckType
|
||||||
): Promise<UpdateInformationType | undefined> {
|
): Promise<UpdateInformationType | undefined> {
|
||||||
const yaml = await getUpdateYaml();
|
const yaml = await getUpdateYaml();
|
||||||
const parsedYaml = parseYaml(yaml);
|
const parsedYaml = parseYaml(yaml);
|
||||||
|
@ -482,7 +517,7 @@ export abstract class Updater {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!forceUpdate && !isVersionNewer(version)) {
|
if (checkType === CheckType.Normal && !isVersionNewer(version)) {
|
||||||
this.logger.info(
|
this.logger.info(
|
||||||
`checkForUpdates: ${version} is not newer than ${packageJson.version}; ` +
|
`checkForUpdates: ${version} is not newer than ${packageJson.version}; ` +
|
||||||
'no new update available'
|
'no new update available'
|
||||||
|
@ -493,7 +528,7 @@ export abstract class Updater {
|
||||||
|
|
||||||
this.logger.info(
|
this.logger.info(
|
||||||
`checkForUpdates: found newer version ${version} ` +
|
`checkForUpdates: found newer version ${version} ` +
|
||||||
`forceUpdate=${forceUpdate}`
|
`checkType=${checkType}`
|
||||||
);
|
);
|
||||||
|
|
||||||
const fileName = getUpdateFileName(
|
const fileName = getUpdateFileName(
|
||||||
|
@ -576,6 +611,7 @@ export abstract class Updater {
|
||||||
const baseUrl = getUpdatesBase();
|
const baseUrl = getUpdatesBase();
|
||||||
const updateFileUrl = `${baseUrl}/${fileName}`;
|
const updateFileUrl = `${baseUrl}/${fileName}`;
|
||||||
|
|
||||||
|
// Show progress on DifferentialOnly, FullOnly, and ForceUpdate
|
||||||
const updateOnProgress = mode !== DownloadMode.Automatic;
|
const updateOnProgress = mode !== DownloadMode.Automatic;
|
||||||
|
|
||||||
const signatureFileName = getSignatureFileName(fileName);
|
const signatureFileName = getSignatureFileName(fileName);
|
||||||
|
|
|
@ -16,7 +16,9 @@ export class MacOSUpdater extends Updater {
|
||||||
// No installers are cache on macOS
|
// No installers are cache on macOS
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async installUpdate(updateFilePath: string): Promise<void> {
|
protected async installUpdate(
|
||||||
|
updateFilePath: string
|
||||||
|
): Promise<() => Promise<void>> {
|
||||||
const { logger } = this;
|
const { logger } = this;
|
||||||
|
|
||||||
logger.info('downloadAndInstall: handing download to electron...');
|
logger.info('downloadAndInstall: handing download to electron...');
|
||||||
|
@ -38,11 +40,11 @@ export class MacOSUpdater extends Updater {
|
||||||
// At this point, closing the app will cause the update to be installed automatically
|
// At this point, closing the app will cause the update to be installed automatically
|
||||||
// because Squirrel has cached the update file and will do the right thing.
|
// because Squirrel has cached the update file and will do the right thing.
|
||||||
|
|
||||||
this.setUpdateListener(async () => {
|
return async () => {
|
||||||
logger.info('downloadAndInstall: restarting...');
|
logger.info('downloadAndInstall: restarting...');
|
||||||
this.markRestarting();
|
this.markRestarting();
|
||||||
autoUpdater.quitAndInstall();
|
autoUpdater.quitAndInstall();
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handToAutoUpdate(filePath: string): Promise<void> {
|
private async handToAutoUpdate(filePath: string): Promise<void> {
|
||||||
|
|
|
@ -46,10 +46,10 @@ export class WindowsUpdater extends Updater {
|
||||||
protected async installUpdate(
|
protected async installUpdate(
|
||||||
updateFilePath: string,
|
updateFilePath: string,
|
||||||
isSilent: boolean
|
isSilent: boolean
|
||||||
): Promise<void> {
|
): Promise<() => Promise<void>> {
|
||||||
const { logger } = this;
|
const { logger } = this;
|
||||||
|
|
||||||
const doInstall = async () => {
|
return async () => {
|
||||||
logger.info('downloadAndInstall: installing...');
|
logger.info('downloadAndInstall: installing...');
|
||||||
try {
|
try {
|
||||||
await this.install(updateFilePath, isSilent);
|
await this.install(updateFilePath, isSilent);
|
||||||
|
@ -64,14 +64,6 @@ export class WindowsUpdater extends Updater {
|
||||||
this.setUpdateListener(this.restart);
|
this.setUpdateListener(this.restart);
|
||||||
this.restart();
|
this.restart();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isSilent) {
|
|
||||||
logger.info('downloadAndInstall: running immediately...');
|
|
||||||
await doInstall();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setUpdateListener(doInstall);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected restart(): void {
|
protected restart(): void {
|
||||||
|
|
Loading…
Reference in a new issue