1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-01-11 18:24:43 +02:00

Desktop: Seamless-Updates: triggering updates (#11079)

This commit is contained in:
Alice 2024-09-21 15:02:22 +03:00 committed by GitHub
parent 99696637b9
commit a56f104fe8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 69 additions and 17 deletions

View File

@ -332,6 +332,10 @@ export default class ElectronAppWrapper {
this.updaterService_.updateApp();
});
ipcMain.on('check-for-updates', () => {
void this.updaterService_.checkForUpdates(true);
});
// Let us register listeners on the window, so we can update the state
// automatically (the listeners will be removed when the window is closed)
// and restore the maximized or full screen state
@ -470,7 +474,7 @@ export default class ElectronAppWrapper {
}
// Electron's autoUpdater has to be init from the main process
public async initializeAutoUpdaterService(logger: LoggerWrapper, devMode: boolean, includePreReleases: boolean) {
public initializeAutoUpdaterService(logger: LoggerWrapper, devMode: boolean, includePreReleases: boolean) {
if (shim.isWindows() || shim.isMac()) {
if (!this.updaterService_) {
this.updaterService_ = new AutoUpdaterService(this.win_, logger, devMode, includePreReleases);
@ -482,7 +486,7 @@ export default class ElectronAppWrapper {
private startPeriodicUpdateCheck = (updateInterval: number = defaultUpdateInterval): void => {
this.stopPeriodicUpdateCheck();
this.updatePollInterval_ = setInterval(() => {
void this.updaterService_.checkForUpdates();
void this.updaterService_.checkForUpdates(false);
}, updateInterval);
setTimeout(this.updaterService_.checkForUpdates, initialUpdateStartup);
};
@ -491,6 +495,7 @@ export default class ElectronAppWrapper {
if (this.updatePollInterval_) {
clearInterval(this.updatePollInterval_);
this.updatePollInterval_ = null;
this.updaterService_ = null;
}
};

View File

@ -404,9 +404,9 @@ class Application extends BaseApplication {
eventManager.on(EventName.ResourceChange, handleResourceChange);
}
private async setupAutoUpdaterService() {
private setupAutoUpdaterService() {
if (Setting.value('featureFlag.autoUpdaterServiceEnabled')) {
await bridge().electronApp().initializeAutoUpdaterService(
bridge().electronApp().initializeAutoUpdaterService(
Logger.create('AutoUpdaterService'),
Setting.value('env') === 'dev',
Setting.value('autoUpdate.includePreReleases'),
@ -449,6 +449,8 @@ class Application extends BaseApplication {
// Loads app-wide styles. (Markdown preview-specific styles loaded in app.js)
await injectCustomStyles('appStyles', Setting.customCssFilePath(Setting.customCssFilenames.JOPLIN_APP));
this.setupAutoUpdaterService();
AlarmService.setDriver(new AlarmServiceDriverNode({ appName: packageInfo.build.appId }));
AlarmService.setLogger(reg.logger());
@ -698,8 +700,6 @@ class Application extends BaseApplication {
SearchEngine.instance().scheduleSyncTables();
});
await this.setupAutoUpdaterService();
// setTimeout(() => {
// void populateDatabase(reg.db(), {
// clearDatabase: true,

View File

@ -26,6 +26,7 @@ import PluginService, { PluginSettings } from '@joplin/lib/services/plugins/Plug
import { getListRendererById, getListRendererIds } from '@joplin/lib/services/noteList/renderers';
import useAsyncEffect from '@joplin/lib/hooks/useAsyncEffect';
import { EventName } from '@joplin/lib/eventManager';
import { ipcRenderer } from 'electron';
const packageInfo: PackageInfo = require('../packageInfo.js');
const { clipboard } = require('electron');
const Menu = bridge().Menu;
@ -575,9 +576,14 @@ function useMenu(props: Props) {
toolsItems.push(SpellCheckerService.instance().spellCheckerConfigMenuItem(props['spellChecker.languages'], props['spellChecker.enabled']));
function _checkForUpdates() {
if (Setting.value('featureFlag.autoUpdaterServiceEnabled')) {
ipcRenderer.send('check-for-updates');
} else {
void checkForUpdates(false, bridge().window(), { includePreReleases: Setting.value('autoUpdate.includePreReleases') });
}
}
function _showAbout() {
const v = versionInfo(packageInfo, PluginService.instance().enabledPlugins(props.pluginSettings));

View File

@ -1,4 +1,4 @@
import { useContext, useCallback, useMemo } from 'react';
import { useContext, useCallback, useMemo, useRef } from 'react';
import { StateLastDeletion } from '@joplin/lib/reducer';
import { _, _n } from '@joplin/lib/locale';
import NotyfContext from '../NotyfContext';
@ -9,6 +9,7 @@ import restoreItems from '@joplin/lib/services/trash/restoreItems';
import { ModelType } from '@joplin/lib/BaseModel';
import { themeStyle } from '@joplin/lib/theme';
import { Dispatch } from 'redux';
import { NotyfNotification } from 'notyf';
interface Props {
lastDeletion: StateLastDeletion;
@ -19,6 +20,7 @@ interface Props {
export default (props: Props) => {
const notyfContext = useContext(NotyfContext);
const notificationRef = useRef<NotyfNotification | null>(null);
const theme = useMemo(() => {
return themeStyle(props.themeId);
@ -39,7 +41,8 @@ export default (props: Props) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const onCancelClick = useCallback(async (event: any) => {
notyf.dismissAll();
notyf.dismiss(notificationRef.current);
notificationRef.current = null;
const lastDeletion: StateLastDeletion = JSON.parse(event.currentTarget.getAttribute('data-lastDeletion'));
@ -70,7 +73,8 @@ export default (props: Props) => {
const linkId = `deletion-notification-cancel-${Math.floor(Math.random() * 1000000)}`;
const cancelLabel = _('Cancel');
notyf.success(`${msg} <a href="#" class="cancel" data-lastDeletion="${htmlentities(JSON.stringify(props.lastDeletion))}" id="${linkId}">${cancelLabel}</a>`);
const notification = notyf.success(`${msg} <a href="#" class="cancel" data-lastDeletion="${htmlentities(JSON.stringify(props.lastDeletion))}" id="${linkId}">${cancelLabel}</a>`);
notificationRef.current = notification;
const element: HTMLAnchorElement = await waitForElement(document, linkId);
if (event.cancelled) return;

View File

@ -5,7 +5,7 @@ import NotyfContext from '../NotyfContext';
import { UpdateInfo } from 'electron-updater';
import { ipcRenderer, IpcRendererEvent } from 'electron';
import { AutoUpdaterEvents } from '../../services/autoUpdater/AutoUpdaterService';
import { NotyfNotification } from 'notyf';
import { NotyfEvent, NotyfNotification } from 'notyf';
import { _ } from '@joplin/lib/locale';
import { htmlentities } from '@joplin/utils/html';
import shim from '@joplin/lib/shim';
@ -16,6 +16,7 @@ interface UpdateNotificationProps {
export enum UpdateNotificationEvents {
ApplyUpdate = 'apply-update',
UpdateNotAvailable = 'update-not-available',
Dismiss = 'dismiss-update-notification',
}
@ -86,17 +87,46 @@ const UpdateNotification = ({ themeId }: UpdateNotificationProps) => {
notificationRef.current = notification;
}, [notyf, theme]);
const handleUpdateNotAvailable = useCallback(() => {
if (notificationRef.current) return;
const noUpdateMessageHtml = htmlentities(_('No updates available'));
const messageHtml = `
<div class="update-notification" style="color: ${theme.color2};">
${noUpdateMessageHtml}
</div>
`;
const notification: NotyfNotification = notyf.open({
type: 'success',
message: messageHtml,
position: {
x: 'right',
y: 'bottom',
},
duration: 5000,
});
notification.on(NotyfEvent.Dismiss, () => {
notificationRef.current = null;
});
notificationRef.current = notification;
}, [notyf, theme]);
useEffect(() => {
ipcRenderer.on(AutoUpdaterEvents.UpdateDownloaded, handleUpdateDownloaded);
ipcRenderer.on(AutoUpdaterEvents.UpdateNotAvailable, handleUpdateNotAvailable);
document.addEventListener(UpdateNotificationEvents.ApplyUpdate, handleApplyUpdate);
document.addEventListener(UpdateNotificationEvents.Dismiss, handleDismissNotification);
return () => {
ipcRenderer.removeListener(AutoUpdaterEvents.UpdateDownloaded, handleUpdateDownloaded);
ipcRenderer.removeListener(AutoUpdaterEvents.UpdateNotAvailable, handleUpdateNotAvailable);
document.removeEventListener(UpdateNotificationEvents.ApplyUpdate, handleApplyUpdate);
document.removeEventListener(UpdateNotificationEvents.Dismiss, handleDismissNotification);
};
}, [handleApplyUpdate, handleDismissNotification, handleUpdateDownloaded]);
}, [handleApplyUpdate, handleDismissNotification, handleUpdateDownloaded, handleUpdateNotAvailable]);
return (

View File

@ -37,7 +37,7 @@ const supportedPlatformAssets: PlatformAssets = {
};
export interface AutoUpdaterServiceInterface {
checkForUpdates(): void;
checkForUpdates(isManualCheck: boolean): void;
updateApp(): void;
fetchLatestRelease(includePreReleases: boolean): Promise<GitHubRelease>;
getDownloadUrlForPlatform(release: GitHubRelease, platform: string, arch: string): string;
@ -48,10 +48,11 @@ export default class AutoUpdaterService implements AutoUpdaterServiceInterface {
private logger_: LoggerWrapper;
private devMode_: boolean;
private enableDevMode = true; // force the updater to work in "dev" mode
private enableAutoDownload = false; // automatically download an update when it is found
private enableAutoDownload = true; // 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;
private isManualCheckInProgress = false;
public constructor(mainWindow: BrowserWindow, logger: LoggerWrapper, devMode: boolean, includePreReleases: boolean) {
this.window_ = mainWindow;
@ -61,8 +62,9 @@ export default class AutoUpdaterService implements AutoUpdaterServiceInterface {
this.configureAutoUpdater();
}
public checkForUpdates = async (): Promise<void> => {
public checkForUpdates = async (isManualCheck = false): Promise<void> => {
try {
this.isManualCheckInProgress = isManualCheck;
await this.checkForLatestRelease();
} catch (error) {
this.logger_.error('Failed to check for updates:', error);
@ -132,6 +134,7 @@ export default class AutoUpdaterService implements AutoUpdaterServiceInterface {
assetUrl = assetUrl.substring(0, assetUrl.lastIndexOf('/'));
autoUpdater.setFeedURL({ provider: 'generic', url: assetUrl });
await autoUpdater.checkForUpdates();
this.isManualCheckInProgress = false;
} catch (error) {
this.logger_.error(`Update download url failed: ${error.message}`);
}
@ -167,6 +170,10 @@ export default class AutoUpdaterService implements AutoUpdaterServiceInterface {
};
private onUpdateNotAvailable = (_info: UpdateInfo): void => {
if (this.isManualCheckInProgress) {
this.window_.webContents.send(AutoUpdaterEvents.UpdateNotAvailable);
}
this.logger_.info('Update not available.');
};

View File

@ -1127,7 +1127,7 @@ const builtInMetadata = (Setting: typeof SettingType) => {
},
autoUpdateEnabled: { value: true, type: SettingItemType.Bool, storage: SettingStorage.File, isGlobal: true, section: 'application', public: platform !== 'linux', appTypes: [AppType.Desktop], label: () => _('Automatically check for updates') },
autoUpdateEnabled: { value: false, type: SettingItemType.Bool, storage: SettingStorage.File, isGlobal: true, section: 'application', public: false, appTypes: [AppType.Desktop], label: () => _('Automatically check for updates') },
'autoUpdate.includePreReleases': { value: false, type: SettingItemType.Bool, section: 'application', storage: SettingStorage.File, isGlobal: true, public: true, appTypes: [AppType.Desktop], label: () => _('Get pre-releases when checking for updates'), description: () => _('See the pre-release page for more details: %s', 'https://joplinapp.org/help/about/prereleases') },
'autoUploadCrashDumps': {