1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00

Desktop: Seamless-Updates: implemented flow for prereleases (#10892)

This commit is contained in:
Alice 2024-08-27 20:04:18 +03:00 committed by GitHub
parent b617a84696
commit 2afc2ca369
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 101 additions and 52 deletions

View File

@ -1,6 +1,6 @@
import Logger, { LoggerWrapper } from '@joplin/utils/Logger';
import { PluginMessage } from './services/plugins/PluginRunner';
import AutoUpdaterService from './services/autoUpdater/AutoUpdaterService';
import AutoUpdaterService, { defaultUpdateInterval, initialUpdateStartup } from './services/autoUpdater/AutoUpdaterService';
import type ShimType from '@joplin/lib/shim';
const shim: typeof ShimType = require('@joplin/lib/shim').default;
import { isCallbackUrl } from '@joplin/lib/callbackUrlUtils';
@ -45,6 +45,7 @@ export default class ElectronAppWrapper {
private initialCallbackUrl_: string = null;
private updaterService_: AutoUpdaterService = null;
private customProtocolHandler_: CustomProtocolHandler = null;
private updatePollInterval_: ReturnType<typeof setTimeout>|null = null;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
public constructor(electronApp: any, env: string, profilePath: string|null, isDebugMode: boolean, initialCallbackUrl: string) {
@ -363,7 +364,7 @@ export default class ElectronAppWrapper {
}
public quit() {
this.stopLookingForUpdates();
this.stopPeriodicUpdateCheck();
this.electronApp_.quit();
}
@ -468,18 +469,30 @@ export default class ElectronAppWrapper {
this.customProtocolHandler_ ??= handleCustomProtocols(logger);
}
public initializeAutoUpdaterService(logger: LoggerWrapper, initializedShim: typeof ShimType, devMode: boolean, includePreReleases: boolean) {
// Electron's autoUpdater has to be init from the main process
public async initializeAutoUpdaterService(logger: LoggerWrapper, devMode: boolean, includePreReleases: boolean) {
if (shim.isWindows() || shim.isMac()) {
this.updaterService_ = new AutoUpdaterService(this.win_, logger, initializedShim, devMode, includePreReleases);
this.updaterService_.startPeriodicUpdateCheck();
if (!this.updaterService_) {
this.updaterService_ = new AutoUpdaterService(this.win_, logger, devMode, includePreReleases);
this.startPeriodicUpdateCheck();
}
}
}
public stopLookingForUpdates() {
if (this.updaterService_ !== null) {
this.updaterService_.stopPeriodicUpdateCheck();
private startPeriodicUpdateCheck = (updateInterval: number = defaultUpdateInterval): void => {
this.stopPeriodicUpdateCheck();
this.updatePollInterval_ = setInterval(() => {
void this.updaterService_.checkForUpdates();
}, updateInterval);
setTimeout(this.updaterService_.checkForUpdates, initialUpdateStartup);
};
private stopPeriodicUpdateCheck = (): void => {
if (this.updatePollInterval_) {
clearInterval(this.updatePollInterval_);
this.updatePollInterval_ = null;
}
}
};
public getCustomProtocolHandler() {
return this.customProtocolHandler_;

View File

@ -404,6 +404,16 @@ class Application extends BaseApplication {
eventManager.on(EventName.ResourceChange, handleResourceChange);
}
private async setupAutoUpdaterService() {
if (Setting.value('featureFlag.autoUpdaterServiceEnabled')) {
await bridge().electronApp().initializeAutoUpdaterService(
Logger.create('AutoUpdaterService'),
Setting.value('env') === 'dev',
Setting.value('autoUpdate.includePreReleases'),
);
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
public async start(argv: string[], startOptions: StartOptions = null): Promise<any> {
// If running inside a package, the command line, instead of being "node.exe <path> <flags>" is "joplin.exe <flags>" so
@ -688,14 +698,7 @@ class Application extends BaseApplication {
SearchEngine.instance().scheduleSyncTables();
});
if (Setting.value('featureFlag.autoUpdaterServiceEnabled')) {
bridge().electronApp().initializeAutoUpdaterService(
Logger.create('AutoUpdaterService'),
shim,
Setting.value('env') === 'dev',
Setting.value('autoUpdate.includePreReleases'),
);
}
await this.setupAutoUpdaterService();
// setTimeout(() => {
// void populateDatabase(reg.db(), {

View File

@ -1,9 +1,11 @@
import { BrowserWindow } from 'electron';
import { autoUpdater, UpdateInfo } from 'electron-updater';
import path = require('path');
import { setInterval } from 'timers';
import Logger, { LoggerWrapper } from '@joplin/utils/Logger';
import type ShimType from '@joplin/lib/shim';
const shim: typeof ShimType = require('@joplin/lib/shim').default;
import { GitHubRelease, GitHubReleaseAsset } from '../../utils/checkForUpdatesUtils';
import * as semver from 'semver';
export enum AutoUpdaterEvents {
CheckingForUpdate = 'checking-for-update',
@ -14,59 +16,36 @@ export enum AutoUpdaterEvents {
UpdateDownloaded = 'update-downloaded',
}
const defaultUpdateInterval = 12 * 60 * 60 * 1000;
const initialUpdateStartup = 5 * 1000;
export const defaultUpdateInterval = 12 * 60 * 60 * 1000;
export const initialUpdateStartup = 5 * 1000;
const releasesLink = 'https://objects.joplinusercontent.com/r/releases';
export interface AutoUpdaterServiceInterface {
startPeriodicUpdateCheck(interval?: number): void;
stopPeriodicUpdateCheck(): void;
checkForUpdates(): void;
updateApp(): void;
}
export default class AutoUpdaterService implements AutoUpdaterServiceInterface {
private window_: BrowserWindow;
private logger_: LoggerWrapper;
private initializedShim_: typeof ShimType;
private devMode_: boolean;
private updatePollInterval_: ReturnType<typeof setInterval>|null = null;
private enableDevMode = true; // force the updater to work in "dev" mode
private enableAutoDownload = false; // automatically download an update when it is found
private autoInstallOnAppQuit = false; // automatically install the downloaded update once the user closes the application
private includePreReleases_ = false;
private allowDowngrade = false;
public constructor(mainWindow: BrowserWindow, logger: LoggerWrapper, initializedShim: typeof ShimType, devMode: boolean, includePreReleases: boolean) {
public constructor(mainWindow: BrowserWindow, logger: LoggerWrapper, devMode: boolean, includePreReleases: boolean) {
this.window_ = mainWindow;
this.logger_ = logger;
this.initializedShim_ = initializedShim;
this.devMode_ = devMode;
this.includePreReleases_ = includePreReleases;
this.configureAutoUpdater();
}
public startPeriodicUpdateCheck = (interval: number = defaultUpdateInterval): void => {
this.stopPeriodicUpdateCheck();
this.updatePollInterval_ = this.initializedShim_.setInterval(() => {
void this.checkForUpdates();
}, interval);
this.initializedShim_.setTimeout(this.checkForUpdates, initialUpdateStartup);
};
public stopPeriodicUpdateCheck = (): void => {
if (this.updatePollInterval_) {
this.initializedShim_.clearInterval(this.updatePollInterval_);
this.updatePollInterval_ = null;
}
};
public checkForUpdates = async (): Promise<void> => {
try {
if (this.includePreReleases_) {
// If this is set to true, then it will compare the versions semantically and it will also look at tags, so we need to manually get the latest pre-release
this.logger_.info('To be implemented...');
} else {
await autoUpdater.checkForUpdates();
}
await this.fetchLatestRelease();
} catch (error) {
this.logger_.error('Failed to check for updates:', error);
if (error.message.includes('ERR_CONNECTION_REFUSED')) {
@ -75,6 +54,63 @@ export default class AutoUpdaterService implements AutoUpdaterServiceInterface {
}
};
public updateApp = (): void => {
autoUpdater.quitAndInstall(false, true);
};
private fetchLatestReleases = async (): Promise<GitHubRelease[]> => {
const response = await fetch(releasesLink);
if (!response.ok) {
const responseText = await response.text();
throw new Error(`Cannot get latest release info: ${responseText.substr(0, 500)}`);
}
return (await response.json()) as GitHubRelease[];
};
private fetchLatestRelease = async (): Promise<void> => {
try {
const releases = await this.fetchLatestReleases();
const sortedReleasesByVersion = releases.sort((a, b) => {
return semver.rcompare(a.tag_name, b.tag_name);
});
const filteredReleases = sortedReleasesByVersion.filter(release => {
return this.includePreReleases_ || !release.prerelease;
});
const release = filteredReleases[0];
if (release) {
let assetUrl = null;
if (shim.isWindows()) {
const asset = release.assets.find((asset: GitHubReleaseAsset) => asset.name === 'latest.yml');
if (asset) {
assetUrl = asset.browser_download_url.replace('/latest.yml', '');
}
} else if (shim.isMac()) {
const asset = release.assets.find((asset: GitHubReleaseAsset) => asset.name === 'latest-mac.yml');
if (asset) {
assetUrl = asset.browser_download_url.replace('/latest-mac.yml', '');
}
}
if (assetUrl) {
autoUpdater.setFeedURL({
provider: 'generic',
url: assetUrl,
});
await autoUpdater.checkForUpdates();
} else {
this.logger_.error('No suitable update asset found for this platform.');
}
}
} catch (error) {
this.logger_.error(error);
}
};
private configureAutoUpdater = (): void => {
autoUpdater.logger = (this.logger_) as Logger;
if (this.devMode_) {
@ -124,8 +160,4 @@ export default class AutoUpdaterService implements AutoUpdaterServiceInterface {
private promptUserToUpdate = async (info: UpdateInfo): Promise<void> => {
this.window_.webContents.send(AutoUpdaterEvents.UpdateDownloaded, info);
};
public updateApp = (): void => {
autoUpdater.quitAndInstall(false, true);
};
}

View File

@ -4,7 +4,7 @@ export interface CheckForUpdateOptions {
includePreReleases?: boolean;
}
interface GitHubReleaseAsset {
export interface GitHubReleaseAsset {
name: string;
browser_download_url: string;
}

View File

@ -129,3 +129,4 @@ entypo
Zocial
agplv
Famegear
rcompare