mirror of
https://github.com/laurent22/joplin.git
synced 2025-01-11 18:24:43 +02:00
Desktop: Seamless-Updates: implemented flow for prereleases (#10892)
This commit is contained in:
parent
b617a84696
commit
2afc2ca369
@ -1,6 +1,6 @@
|
|||||||
import Logger, { LoggerWrapper } from '@joplin/utils/Logger';
|
import Logger, { LoggerWrapper } from '@joplin/utils/Logger';
|
||||||
import { PluginMessage } from './services/plugins/PluginRunner';
|
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';
|
import type ShimType from '@joplin/lib/shim';
|
||||||
const shim: typeof ShimType = require('@joplin/lib/shim').default;
|
const shim: typeof ShimType = require('@joplin/lib/shim').default;
|
||||||
import { isCallbackUrl } from '@joplin/lib/callbackUrlUtils';
|
import { isCallbackUrl } from '@joplin/lib/callbackUrlUtils';
|
||||||
@ -45,6 +45,7 @@ export default class ElectronAppWrapper {
|
|||||||
private initialCallbackUrl_: string = null;
|
private initialCallbackUrl_: string = null;
|
||||||
private updaterService_: AutoUpdaterService = null;
|
private updaterService_: AutoUpdaterService = null;
|
||||||
private customProtocolHandler_: CustomProtocolHandler = 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
|
// 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) {
|
public constructor(electronApp: any, env: string, profilePath: string|null, isDebugMode: boolean, initialCallbackUrl: string) {
|
||||||
@ -363,7 +364,7 @@ export default class ElectronAppWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public quit() {
|
public quit() {
|
||||||
this.stopLookingForUpdates();
|
this.stopPeriodicUpdateCheck();
|
||||||
this.electronApp_.quit();
|
this.electronApp_.quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -468,18 +469,30 @@ export default class ElectronAppWrapper {
|
|||||||
this.customProtocolHandler_ ??= handleCustomProtocols(logger);
|
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()) {
|
if (shim.isWindows() || shim.isMac()) {
|
||||||
this.updaterService_ = new AutoUpdaterService(this.win_, logger, initializedShim, devMode, includePreReleases);
|
if (!this.updaterService_) {
|
||||||
this.updaterService_.startPeriodicUpdateCheck();
|
this.updaterService_ = new AutoUpdaterService(this.win_, logger, devMode, includePreReleases);
|
||||||
|
this.startPeriodicUpdateCheck();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public stopLookingForUpdates() {
|
private startPeriodicUpdateCheck = (updateInterval: number = defaultUpdateInterval): void => {
|
||||||
if (this.updaterService_ !== null) {
|
this.stopPeriodicUpdateCheck();
|
||||||
this.updaterService_.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() {
|
public getCustomProtocolHandler() {
|
||||||
return this.customProtocolHandler_;
|
return this.customProtocolHandler_;
|
||||||
|
@ -404,6 +404,16 @@ class Application extends BaseApplication {
|
|||||||
eventManager.on(EventName.ResourceChange, handleResourceChange);
|
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
|
// 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> {
|
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
|
// 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();
|
SearchEngine.instance().scheduleSyncTables();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (Setting.value('featureFlag.autoUpdaterServiceEnabled')) {
|
await this.setupAutoUpdaterService();
|
||||||
bridge().electronApp().initializeAutoUpdaterService(
|
|
||||||
Logger.create('AutoUpdaterService'),
|
|
||||||
shim,
|
|
||||||
Setting.value('env') === 'dev',
|
|
||||||
Setting.value('autoUpdate.includePreReleases'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// setTimeout(() => {
|
// setTimeout(() => {
|
||||||
// void populateDatabase(reg.db(), {
|
// void populateDatabase(reg.db(), {
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import { BrowserWindow } from 'electron';
|
import { BrowserWindow } from 'electron';
|
||||||
import { autoUpdater, UpdateInfo } from 'electron-updater';
|
import { autoUpdater, UpdateInfo } from 'electron-updater';
|
||||||
import path = require('path');
|
import path = require('path');
|
||||||
import { setInterval } from 'timers';
|
|
||||||
import Logger, { LoggerWrapper } from '@joplin/utils/Logger';
|
import Logger, { LoggerWrapper } from '@joplin/utils/Logger';
|
||||||
import type ShimType from '@joplin/lib/shim';
|
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 {
|
export enum AutoUpdaterEvents {
|
||||||
CheckingForUpdate = 'checking-for-update',
|
CheckingForUpdate = 'checking-for-update',
|
||||||
@ -14,59 +16,36 @@ export enum AutoUpdaterEvents {
|
|||||||
UpdateDownloaded = 'update-downloaded',
|
UpdateDownloaded = 'update-downloaded',
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultUpdateInterval = 12 * 60 * 60 * 1000;
|
export const defaultUpdateInterval = 12 * 60 * 60 * 1000;
|
||||||
const initialUpdateStartup = 5 * 1000;
|
export const initialUpdateStartup = 5 * 1000;
|
||||||
|
const releasesLink = 'https://objects.joplinusercontent.com/r/releases';
|
||||||
|
|
||||||
export interface AutoUpdaterServiceInterface {
|
export interface AutoUpdaterServiceInterface {
|
||||||
startPeriodicUpdateCheck(interval?: number): void;
|
|
||||||
stopPeriodicUpdateCheck(): void;
|
|
||||||
checkForUpdates(): void;
|
checkForUpdates(): void;
|
||||||
|
updateApp(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class AutoUpdaterService implements AutoUpdaterServiceInterface {
|
export default class AutoUpdaterService implements AutoUpdaterServiceInterface {
|
||||||
private window_: BrowserWindow;
|
private window_: BrowserWindow;
|
||||||
private logger_: LoggerWrapper;
|
private logger_: LoggerWrapper;
|
||||||
private initializedShim_: typeof ShimType;
|
|
||||||
private devMode_: boolean;
|
private devMode_: boolean;
|
||||||
private updatePollInterval_: ReturnType<typeof setInterval>|null = null;
|
|
||||||
private enableDevMode = true; // force the updater to work in "dev" mode
|
private enableDevMode = true; // force the updater to work in "dev" mode
|
||||||
private enableAutoDownload = false; // automatically download an update when it is found
|
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 autoInstallOnAppQuit = false; // automatically install the downloaded update once the user closes the application
|
||||||
private includePreReleases_ = false;
|
private includePreReleases_ = false;
|
||||||
private allowDowngrade = 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.window_ = mainWindow;
|
||||||
this.logger_ = logger;
|
this.logger_ = logger;
|
||||||
this.initializedShim_ = initializedShim;
|
|
||||||
this.devMode_ = devMode;
|
this.devMode_ = devMode;
|
||||||
this.includePreReleases_ = includePreReleases;
|
this.includePreReleases_ = includePreReleases;
|
||||||
this.configureAutoUpdater();
|
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> => {
|
public checkForUpdates = async (): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
if (this.includePreReleases_) {
|
await this.fetchLatestRelease();
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger_.error('Failed to check for updates:', error);
|
this.logger_.error('Failed to check for updates:', error);
|
||||||
if (error.message.includes('ERR_CONNECTION_REFUSED')) {
|
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 => {
|
private configureAutoUpdater = (): void => {
|
||||||
autoUpdater.logger = (this.logger_) as Logger;
|
autoUpdater.logger = (this.logger_) as Logger;
|
||||||
if (this.devMode_) {
|
if (this.devMode_) {
|
||||||
@ -124,8 +160,4 @@ export default class AutoUpdaterService implements AutoUpdaterServiceInterface {
|
|||||||
private promptUserToUpdate = async (info: UpdateInfo): Promise<void> => {
|
private promptUserToUpdate = async (info: UpdateInfo): Promise<void> => {
|
||||||
this.window_.webContents.send(AutoUpdaterEvents.UpdateDownloaded, info);
|
this.window_.webContents.send(AutoUpdaterEvents.UpdateDownloaded, info);
|
||||||
};
|
};
|
||||||
|
|
||||||
public updateApp = (): void => {
|
|
||||||
autoUpdater.quitAndInstall(false, true);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ export interface CheckForUpdateOptions {
|
|||||||
includePreReleases?: boolean;
|
includePreReleases?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GitHubReleaseAsset {
|
export interface GitHubReleaseAsset {
|
||||||
name: string;
|
name: string;
|
||||||
browser_download_url: string;
|
browser_download_url: string;
|
||||||
}
|
}
|
||||||
|
@ -129,3 +129,4 @@ entypo
|
|||||||
Zocial
|
Zocial
|
||||||
agplv
|
agplv
|
||||||
Famegear
|
Famegear
|
||||||
|
rcompare
|
Loading…
Reference in New Issue
Block a user