From d9a4a9cb3074fccd6bcefb9d93427f0802a900c9 Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Wed, 20 Apr 2022 17:34:58 +0100 Subject: [PATCH] Desktop: Ask to start in safe mode when the application has crashed --- .eslintignore | 3 +++ .gitignore | 3 +++ packages/app-desktop/ElectronAppWrapper.ts | 8 +++---- packages/app-desktop/app.ts | 11 ++++++++++ .../app-desktop/commands/switchProfile.ts | 4 ++-- .../app-desktop/commands/toggleSafeMode.ts | 4 ++-- .../gui/ConfigScreen/ConfigScreen.tsx | 7 ++++--- packages/app-desktop/gui/ErrorBoundary.tsx | 4 ++-- .../app-desktop/gui/MainScreen/MainScreen.tsx | 21 ++++++++++++------- .../gui/MainScreen/commands/addProfile.ts | 4 ++-- .../gui/Root_UpgradeSyncTarget.tsx | 4 ++-- packages/app-desktop/services/restart.ts | 10 +++++++++ packages/lib/models/Setting.ts | 6 ++++++ 13 files changed, 64 insertions(+), 25 deletions(-) create mode 100644 packages/app-desktop/services/restart.ts diff --git a/.eslintignore b/.eslintignore index feca7cba5..527f9e0dd 100644 --- a/.eslintignore +++ b/.eslintignore @@ -808,6 +808,9 @@ packages/app-desktop/services/plugins/hooks/useViewIsReady.js.map packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.d.ts packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.js packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.js.map +packages/app-desktop/services/restart.d.ts +packages/app-desktop/services/restart.js +packages/app-desktop/services/restart.js.map packages/app-desktop/services/share/invitationRespond.d.ts packages/app-desktop/services/share/invitationRespond.js packages/app-desktop/services/share/invitationRespond.js.map diff --git a/.gitignore b/.gitignore index 0c01599e3..d2b877d8d 100644 --- a/.gitignore +++ b/.gitignore @@ -798,6 +798,9 @@ packages/app-desktop/services/plugins/hooks/useViewIsReady.js.map packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.d.ts packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.js packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.js.map +packages/app-desktop/services/restart.d.ts +packages/app-desktop/services/restart.js +packages/app-desktop/services/restart.js.map packages/app-desktop/services/share/invitationRespond.d.ts packages/app-desktop/services/share/invitationRespond.js packages/app-desktop/services/share/invitationRespond.js.map diff --git a/packages/app-desktop/ElectronAppWrapper.ts b/packages/app-desktop/ElectronAppWrapper.ts index d57245fe5..ef3fb3d16 100644 --- a/packages/app-desktop/ElectronAppWrapper.ts +++ b/packages/app-desktop/ElectronAppWrapper.ts @@ -192,7 +192,7 @@ export default class ElectronAppWrapper { // We got the response from the renderer process: // save the response and try quit again. this.rendererProcessQuitReply_ = args; - this.electronApp_.quit(); + this.quit(); } }); @@ -253,7 +253,7 @@ export default class ElectronAppWrapper { }); } - async quit() { + quit() { this.electronApp_.quit(); } @@ -325,7 +325,7 @@ export default class ElectronAppWrapper { if (!gotTheLock) { // Another instance is already running - exit - this.electronApp_.quit(); + this.quit(); return true; } @@ -362,7 +362,7 @@ export default class ElectronAppWrapper { }); this.electronApp_.on('window-all-closed', () => { - this.electronApp_.quit(); + this.quit(); }); this.electronApp_.on('activate', () => { diff --git a/packages/app-desktop/app.ts b/packages/app-desktop/app.ts index 183291ba2..781e14ab5 100644 --- a/packages/app-desktop/app.ts +++ b/packages/app-desktop/app.ts @@ -324,6 +324,15 @@ class Application extends BaseApplication { }, 500); } + private crashDetectionHandler() { + if (!Setting.value('wasClosedSuccessfully')) { + const answer = confirm(_('The application did not close properly. Would you like to start in safe mode?')); + Setting.setValue('isSafeMode', !!answer); + } + + Setting.setValue('wasClosedSuccessfully', false); + } + public async start(argv: string[]): Promise { // If running inside a package, the command line, instead of being "node.exe " is "joplin.exe " so // insert an extra argument so that they can be processed in a consistent way everywhere. @@ -331,6 +340,8 @@ class Application extends BaseApplication { argv = await super.start(argv); + this.crashDetectionHandler(); + await this.applySettingsSideEffects(); if (Setting.value('sync.upgradeState') === Setting.SYNC_UPGRADE_STATE_MUST_DO) { diff --git a/packages/app-desktop/commands/switchProfile.ts b/packages/app-desktop/commands/switchProfile.ts index 2f55be340..c0bd06a5c 100644 --- a/packages/app-desktop/commands/switchProfile.ts +++ b/packages/app-desktop/commands/switchProfile.ts @@ -2,7 +2,7 @@ import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/ import Setting from '@joplin/lib/models/Setting'; import { saveProfileConfig } from '@joplin/lib/services/profileConfig'; import { ProfileConfig } from '@joplin/lib/services/profileConfig/types'; -import bridge from '../services/bridge'; +import restart from '../services/restart'; export const declaration: CommandDeclaration = { name: 'switchProfile', @@ -20,7 +20,7 @@ export const runtime = (): CommandRuntime => { }; await saveProfileConfig(`${Setting.value('rootProfileDir')}/profiles.json`, newConfig); - bridge().restart(false); + await restart(false); }, }; }; diff --git a/packages/app-desktop/commands/toggleSafeMode.ts b/packages/app-desktop/commands/toggleSafeMode.ts index 91d21c9b9..9d1df1a07 100644 --- a/packages/app-desktop/commands/toggleSafeMode.ts +++ b/packages/app-desktop/commands/toggleSafeMode.ts @@ -1,7 +1,7 @@ import { _ } from '@joplin/lib/locale'; import Setting from '@joplin/lib/models/Setting'; import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService'; -import bridge from '../services/bridge'; +import restart from '../services/restart'; export const declaration: CommandDeclaration = { name: 'toggleSafeMode', @@ -14,7 +14,7 @@ export const runtime = (): CommandRuntime => { enabled = enabled !== null ? enabled : !Setting.value('isSafeMode'); Setting.setValue('isSafeMode', enabled); await Setting.saveAll(); - bridge().restart(); + await restart(); }, }; }; diff --git a/packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx b/packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx index 596145e90..866006636 100644 --- a/packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx +++ b/packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx @@ -14,6 +14,7 @@ const pathUtils = require('@joplin/lib/path-utils'); import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry'; const shared = require('@joplin/lib/components/shared/config-shared.js'); import ClipperConfigScreen from '../ClipperConfigScreen'; +import restart from '../../services/restart'; const { KeymapConfigScreen } = require('../KeymapConfig/KeymapConfigScreen'); const settingKeyToControl: any = { @@ -72,12 +73,12 @@ class ConfigScreenComponent extends React.Component { if (!confirm('This cannot be undone. Do you want to continue?')) return; Setting.setValue('sync.startupOperation', SyncStartupOperation.ClearLocalSyncState); await Setting.saveAll(); - bridge().restart(); + await restart(); } else if (key === 'sync.clearLocalDataButton') { if (!confirm('This cannot be undone. Do you want to continue?')) return; Setting.setValue('sync.startupOperation', SyncStartupOperation.ClearLocalData); await Setting.saveAll(); - bridge().restart(); + await restart(); } else if (key === 'sync.openSyncWizard') { this.props.dispatch({ type: 'DIALOG_OPEN', @@ -632,7 +633,7 @@ class ConfigScreenComponent extends React.Component { private async restartApp() { await Setting.saveAll(); - bridge().restart(); + await restart(); } private async checkNeedRestart() { diff --git a/packages/app-desktop/gui/ErrorBoundary.tsx b/packages/app-desktop/gui/ErrorBoundary.tsx index d9f7c12cd..5b2d1dbc3 100644 --- a/packages/app-desktop/gui/ErrorBoundary.tsx +++ b/packages/app-desktop/gui/ErrorBoundary.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import versionInfo from '@joplin/lib/versionInfo'; import PluginService from '@joplin/lib/services/plugins/PluginService'; import Setting from '@joplin/lib/models/Setting'; -import bridge from '../services/bridge'; +import restart from '../services/restart'; const packageInfo = require('../packageInfo.js'); const ipcRenderer = require('electron').ipcRenderer; @@ -75,7 +75,7 @@ export default class ErrorBoundary extends React.Component { const safeMode_click = async () => { Setting.setValue('isSafeMode', true); await Setting.saveAll(); - bridge().restart(); + await restart(); }; try { diff --git a/packages/app-desktop/gui/MainScreen/MainScreen.tsx b/packages/app-desktop/gui/MainScreen/MainScreen.tsx index eb4bd48d0..0f1e5648a 100644 --- a/packages/app-desktop/gui/MainScreen/MainScreen.tsx +++ b/packages/app-desktop/gui/MainScreen/MainScreen.tsx @@ -40,6 +40,7 @@ import { showMissingMasterKeyMessage } from '@joplin/lib/services/e2ee/utils'; import { MasterKeyEntity } from '@joplin/lib/services/e2ee/types'; import commands from './commands/index'; import invitationRespond from '../../services/share/invitationRespond'; +import restart from '../../services/restart'; const { connect } = require('react-redux'); const { PromptDialog } = require('../PromptDialog.min.js'); const NotePropertiesDialog = require('../NotePropertiesDialog.min.js'); @@ -267,18 +268,22 @@ class MainScreenComponent extends React.Component { if (this.waitForNotesSavedIID_) shim.clearInterval(this.waitForNotesSavedIID_); this.waitForNotesSavedIID_ = null; - ipcRenderer.send('asynchronous-message', 'appCloseReply', { - canClose: !this.props.hasNotesBeingSaved, - }); + const sendCanClose = async (canClose: boolean) => { + if (canClose) { + Setting.setValue('wasClosedSuccessfully', true); + await Setting.saveAll(); + } + ipcRenderer.send('asynchronous-message', 'appCloseReply', { canClose }); + }; + + await sendCanClose(!this.props.hasNotesBeingSaved); if (this.props.hasNotesBeingSaved) { this.waitForNotesSavedIID_ = shim.setInterval(() => { if (!this.props.hasNotesBeingSaved) { shim.clearInterval(this.waitForNotesSavedIID_); this.waitForNotesSavedIID_ = null; - ipcRenderer.send('asynchronous-message', 'appCloseReply', { - canClose: true, - }); + void sendCanClose(true); } }, 50); } @@ -557,13 +562,13 @@ class MainScreenComponent extends React.Component { const onRestartAndUpgrade = async () => { Setting.setValue('sync.upgradeState', Setting.SYNC_UPGRADE_STATE_MUST_DO); await Setting.saveAll(); - bridge().restart(); + await restart(); }; const onDisableSafeModeAndRestart = async () => { Setting.setValue('isSafeMode', false); await Setting.saveAll(); - bridge().restart(); + await restart(); }; const onInvitationRespond = async (shareUserId: string, folderId: string, masterKey: MasterKeyEntity, accept: boolean) => { diff --git a/packages/app-desktop/gui/MainScreen/commands/addProfile.ts b/packages/app-desktop/gui/MainScreen/commands/addProfile.ts index a9af607c9..0153c811a 100644 --- a/packages/app-desktop/gui/MainScreen/commands/addProfile.ts +++ b/packages/app-desktop/gui/MainScreen/commands/addProfile.ts @@ -2,7 +2,7 @@ import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/ import { _ } from '@joplin/lib/locale'; import { createNewProfile, saveProfileConfig } from '@joplin/lib/services/profileConfig'; import Setting from '@joplin/lib/models/Setting'; -import bridge from '../../../services/bridge'; +import restart from '../../../services/restart'; export const declaration: CommandDeclaration = { name: 'addProfile', @@ -22,7 +22,7 @@ export const runtime = (comp: any): CommandRuntime => { const { newConfig, newProfile } = createNewProfile(context.state.profileConfig, answer); newConfig.currentProfileId = newProfile.id; await saveProfileConfig(`${Setting.value('rootProfileDir')}/profiles.json`, newConfig); - bridge().restart(false); + await restart(false); } comp.setState({ promptOptions: null }); diff --git a/packages/app-desktop/gui/Root_UpgradeSyncTarget.tsx b/packages/app-desktop/gui/Root_UpgradeSyncTarget.tsx index 09b9b7bb6..7386a6926 100644 --- a/packages/app-desktop/gui/Root_UpgradeSyncTarget.tsx +++ b/packages/app-desktop/gui/Root_UpgradeSyncTarget.tsx @@ -5,7 +5,7 @@ import useSyncTargetUpgrade, { SyncTargetUpgradeResult } from '@joplin/lib/servi const { render } = require('react-dom'); const ipcRenderer = require('electron').ipcRenderer; import Setting from '@joplin/lib/models/Setting'; -const bridge = require('@electron/remote').require('./bridge').default; +import restart from '../services/restart'; function useAppCloseHandler(upgradeResult: SyncTargetUpgradeResult) { useEffect(function() { @@ -64,7 +64,7 @@ function useStyle() { function useRestartOnDone(upgradeResult: SyncTargetUpgradeResult) { useEffect(function() { if (upgradeResult.done && !upgradeResult.error) { - bridge().restart(); + void restart(); } }, [upgradeResult.done]); } diff --git a/packages/app-desktop/services/restart.ts b/packages/app-desktop/services/restart.ts new file mode 100644 index 000000000..24bc97954 --- /dev/null +++ b/packages/app-desktop/services/restart.ts @@ -0,0 +1,10 @@ +import Setting from '@joplin/lib/models/Setting'; +import bridge from './bridge'; + + +export default async (linuxSafeRestart: boolean = true) => { + Setting.setValue('wasClosedSuccessfully', true); + await Setting.saveAll(); + + bridge().restart(linuxSafeRestart); +}; diff --git a/packages/lib/models/Setting.ts b/packages/lib/models/Setting.ts index a6ad2645d..6cf22f9c6 100644 --- a/packages/lib/models/Setting.ts +++ b/packages/lib/models/Setting.ts @@ -1500,6 +1500,12 @@ class Setting extends BaseModel { public: false, }, + wasClosedSuccessfully: { + value: true, + type: SettingItemType.Bool, + public: false, + }, + // 'featureFlag.syncAccurateTimestamps': { // value: false, // type: SettingItemType.Bool,