Install downloaded updates while in tray

This commit is contained in:
Fedor Indutny 2023-12-12 03:15:36 +01:00 committed by GitHub
parent c4521a063c
commit 12a2ec8dd4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 87 additions and 24 deletions

View file

@ -117,6 +117,10 @@ export class SystemTrayService {
this.isQuitting = true;
}
isVisible(): boolean {
return this.tray !== undefined;
}
private render(): void {
if (this.isEnabled && this.browserWindow) {
this.renderEnabled();

View file

@ -1161,7 +1161,17 @@ async function readyForUpdates() {
settingsChannel !== undefined,
'SettingsChannel must be initialized'
);
await updater.start(settingsChannel, getLogger(), getMainWindow);
await updater.start({
settingsChannel,
logger: getLogger(),
getMainWindow,
canRunSilently: () => {
return (
systemTrayService?.isVisible() === true &&
mainWindow?.isVisible() === false
);
},
});
} catch (error) {
getLogger().error(
'Error starting update checks:',

View file

@ -45,3 +45,17 @@ ManifestDPIAware true
end_semver_check:
!macroend
!macro customInstall
${If} ${Silent}
${AndIf} ${isUpdated}
# Copied from app-builder-lib templates/nsis/common.nsh:
# "otherwise app window will be in background"
HideWindow
# Signal modification: '--start-in-tray' added
${StdUtils.ExecShellAsUser} $0 "$launchLink" "open" \
"--updated --start-in-tray"
${EndIf}
!macroend

View file

@ -84,6 +84,13 @@ type DownloadUpdateResultType = Readonly<{
signature: Buffer;
}>;
export type UpdaterOptionsType = Readonly<{
settingsChannel: SettingsChannel;
logger: LoggerType;
getMainWindow: () => BrowserWindow | undefined;
canRunSilently: () => boolean;
}>;
export abstract class Updater {
protected fileName: string | undefined;
@ -91,17 +98,31 @@ export abstract class Updater {
protected cachedDifferentialData: DifferentialDownloadDataType | undefined;
protected readonly logger: LoggerType;
private readonly settingsChannel: SettingsChannel;
protected readonly getMainWindow: () => BrowserWindow | undefined;
private throttledSendDownloadingUpdate: (downloadedSize: number) => void;
private activeDownload: Promise<boolean> | undefined;
private markedCannotUpdate = false;
constructor(
protected readonly logger: LoggerType,
private readonly settingsChannel: SettingsChannel,
protected readonly getMainWindow: () => BrowserWindow | undefined
) {
private readonly canRunSilently: () => boolean;
constructor({
settingsChannel,
logger,
getMainWindow,
canRunSilently,
}: UpdaterOptionsType) {
this.settingsChannel = settingsChannel;
this.logger = logger;
this.getMainWindow = getMainWindow;
this.canRunSilently = canRunSilently;
this.throttledSendDownloadingUpdate = throttle((downloadedSize: number) => {
const mainWindow = this.getMainWindow();
mainWindow?.webContents.send(
@ -141,7 +162,10 @@ export abstract class Updater {
protected abstract deletePreviousInstallers(): Promise<void>;
protected abstract installUpdate(updateFilePath: string): Promise<void>;
protected abstract installUpdate(
updateFilePath: string,
isSilent: boolean
): Promise<void>;
//
// Protected methods
@ -255,7 +279,7 @@ export abstract class Updater {
);
}
await this.installUpdate(updateFilePath);
await this.installUpdate(updateFilePath, this.canRunSilently());
const mainWindow = this.getMainWindow();
if (mainWindow) {

View file

@ -2,26 +2,20 @@
// SPDX-License-Identifier: AGPL-3.0-only
import config from 'config';
import type { BrowserWindow } from 'electron';
import type { Updater } from './common';
import type { Updater, UpdaterOptionsType } from './common';
import { MacOSUpdater } from './macos';
import { WindowsUpdater } from './windows';
import { isLinuxVersionSupported } from './linux';
import type { LoggerType } from '../types/Logging';
import { DialogType } from '../types/Dialogs';
import type { SettingsChannel } from '../main/settingsChannel';
let initialized = false;
let updater: Updater | undefined;
export async function start(
settingsChannel: SettingsChannel,
logger: LoggerType,
getMainWindow: () => BrowserWindow | undefined
): Promise<void> {
export async function start(options: UpdaterOptionsType): Promise<void> {
const { platform } = process;
const { logger, getMainWindow } = options;
if (initialized) {
throw new Error('updater/start: Updates have already been initialized!');
@ -50,9 +44,9 @@ export async function start(
}
if (platform === 'win32') {
updater = new WindowsUpdater(logger, settingsChannel, getMainWindow);
updater = new WindowsUpdater(options);
} else if (platform === 'darwin') {
updater = new MacOSUpdater(logger, settingsChannel, getMainWindow);
updater = new MacOSUpdater(options);
} else {
throw new Error('updater/start: Unsupported platform');
}

View file

@ -44,13 +44,16 @@ export class WindowsUpdater extends Updater {
})
);
}
protected async installUpdate(updateFilePath: string): Promise<void> {
protected async installUpdate(
updateFilePath: string,
isSilent: boolean
): Promise<void> {
const { logger } = this;
this.setUpdateListener(async () => {
const doInstall = async () => {
logger.info('downloadAndInstall: installing...');
try {
await this.install(updateFilePath);
await this.install(updateFilePath, isSilent);
this.installing = true;
} catch (error) {
this.markCannotUpdate(error);
@ -61,10 +64,18 @@ export class WindowsUpdater extends Updater {
logger.info('downloadAndInstall: restarting...');
markShouldQuit();
app.quit();
});
};
if (isSilent) {
logger.info('downloadAndInstall: running immediately...');
await doInstall();
return;
}
this.setUpdateListener(doInstall);
}
private async install(filePath: string): Promise<void> {
private async install(filePath: string, isSilent: boolean): Promise<void> {
if (this.installing) {
return;
}
@ -73,6 +84,12 @@ export class WindowsUpdater extends Updater {
logger.info('windows/install: installing package...');
const args = ['--updated'];
if (isSilent) {
// App isn't automatically restarted with "/S" flag, but "--updated"
// will trigger our code in `build/installer.nsh` that will start the app
// with "--start-in-tray" flag (see `app/main.ts`)
args.push('/S');
}
const options = {
detached: true,
stdio: 'ignore' as const, // TypeScript considers this a plain string without help