diff --git a/src/main/di.ts b/src/main/di.ts index 45907aee8b..bff54b3ea2 100644 --- a/src/main/di.ts +++ b/src/main/di.ts @@ -80,6 +80,14 @@ if (!fs.existsSync(configDataFolderPath)) { fs.mkdirSync(configDataFolderPath); } +const WATCHDOG_FILENAME = "watchdog.json"; +export const watchdogFilePath = path.join( + configDataFolderPath, + WATCHDOG_FILENAME, +); + + + const STATE_FILENAME = "state.json"; export const stateFilePath = path.join( configDataFolderPath, diff --git a/src/main/redux/sagas/index.ts b/src/main/redux/sagas/index.ts index 1316338f1d..741ddb33c0 100644 --- a/src/main/redux/sagas/index.ts +++ b/src/main/redux/sagas/index.ts @@ -42,6 +42,8 @@ import { sagaCustomizationProfileProvisioning } from "./customization"; import isURL from "validator/lib/isURL"; import { publicationIntegrityChecker } from "./publication/checker"; import { error } from "readium-desktop/main/tools/error"; +import { watchdog } from "./watchdog"; +import { diMainGet } from "readium-desktop/main/di"; // Logger const filename_ = "readium-desktop:main:saga:app"; @@ -64,6 +66,11 @@ export function* rootSaga() { call(appSaga.init), call(keyboardShortcuts.init), call(sagaCustomizationProfileProvisioning), + call(function*() { + + const state = diMainGet("store").getState(); + debug("__TIME__", (state as any).__t, new Date((state as any).__t)); + }), ]); } catch (e) { @@ -89,6 +96,14 @@ export function* rootSaga() { } }); + yield* spawnTyped(function* () { + try { + yield* callTyped(watchdog); + } catch (e) { + error(filename_, e); + } + }); + // customization profile acquire, provisioned and activate yield customization.saga(); diff --git a/src/main/redux/sagas/persist.ts b/src/main/redux/sagas/persist.ts index 3f1a85b3de..8180423166 100644 --- a/src/main/redux/sagas/persist.ts +++ b/src/main/redux/sagas/persist.ts @@ -23,6 +23,7 @@ import { takeSpawnEvery } from "readium-desktop/common/redux/sagas/takeSpawnEver import { ReaderConfig } from "readium-desktop/common/models/reader"; import { IDictWinRegistryReaderState } from "../states/win/registry/reader"; import { _APP_VERSION } from "readium-desktop/preprocessor-directives"; +import { resetWatchdog } from "./watchdog"; const DEBOUNCE_TIME = 3 * 60 * 1000; // 3 min const PUBLICATION_STORAGE_DEBOUNCE_TIME = 10 * 1000; // 10 secs @@ -102,6 +103,8 @@ export function* needToPersistFinalState() { const nextState = yield* selectTyped((store: RootState) => store); yield call(() => persistStateToFs(nextState)); yield call(() => needToPersistPatch()); + + yield call(() => resetWatchdog()); } export function* needToPersistPatch() { diff --git a/src/main/redux/sagas/watchdog.ts b/src/main/redux/sagas/watchdog.ts new file mode 100644 index 0000000000..d678c42906 --- /dev/null +++ b/src/main/redux/sagas/watchdog.ts @@ -0,0 +1,51 @@ +// ==LICENSE-BEGIN== +// Copyright 2017 European Digital Reading Lab. All rights reserved. +// Licensed to the Readium Foundation under one or more contributor license agreements. +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file exposed on Github (readium) in the project repository. +// ==LICENSE-END== + +import * as fs from "node:fs"; +import debug_ from "debug"; +import { call as callTyped, delay as delayTyped, SagaGenerator } from "typed-redux-saga"; +import { watchdogFilePath } from "readium-desktop/main/di"; +import { ok } from "readium-desktop/common/utils/assert"; + +// Logger +const filename_ = "readium-desktop:main:redux:sagas:watchdog"; +const debug = debug_(filename_); +debug("_"); + +export const watchdog = function* (): SagaGenerator { + + let counter = 0; + + while (1) { + counter++; + if (counter >= Number.MAX_SAFE_INTEGER) { + counter = 0; + } + const time = (new Date()).toISOString(); + const jsonObj = {time, counter}; + const data = JSON.stringify(jsonObj); + // debug("WATCHDOG", jsonObj); + + yield* delayTyped(1000); // 1s + yield* callTyped(async () => await fs.promises.writeFile(watchdogFilePath, data, { encoding: "utf-8" })); + const dataRead = yield* callTyped(async () => await fs.promises.readFile(watchdogFilePath, { encoding: "utf8" })); + ok(dataRead === data, "Watchdog error !?"); + } +}; + +export const resetWatchdog = () => { + + fs.rmSync(watchdogFilePath); +}; + +export const readWatchdog = async () => { + + const dataRead = await fs.promises.readFile(watchdogFilePath, { encoding: "utf8" }); + const jsonObj = JSON.parse(dataRead); + const time = new Date(jsonObj.time); + return time; +}; diff --git a/src/main/redux/store/memory.ts b/src/main/redux/store/memory.ts index 26d778eb6b..82c48c12ea 100644 --- a/src/main/redux/store/memory.ts +++ b/src/main/redux/store/memory.ts @@ -33,6 +33,8 @@ import { IReaderStateReaderSession } from "readium-desktop/common/redux/states/r import { IWinRegistryReaderState } from "readium-desktop/main/redux/states/win/registry/reader"; import { ReaderConfig } from "readium-desktop/common/models/reader"; import { IRTLFlipState } from "readium-desktop/common/redux/states/renderer/rtlFlip"; +import { readWatchdog } from "../sagas/watchdog"; +import { dialog } from "electron"; // import { composeWithDevTools } from "remote-redux-devtools"; const REDUX_REMOTE_DEVTOOLS_PORT = 7770; @@ -113,8 +115,35 @@ export async function initStore() const jsonStr = await fs.promises.readFile(stateFilePath, { encoding: "utf8" }); const json = JSON.parse(jsonStr); - if (test(json)) + if (test(json)) { reduxState = json; + } + + try { + const timestr = (reduxState as any).__t; + const version = (reduxState as any).__v; + debug(`Last state.json saved time: ${timestr} from v=${version}`); + delete (reduxState as any).__t; + delete (reduxState as any).__v; + + const wdTime = await readWatchdog(); + debug(`Watchdog time: ${wdTime.toUTCString()}`); + + if (wdTime) { + debug("A crash occurred. The watchdog file must be removed when Thorium closes."); + const p = dialog.showMessageBox(undefined, { message: "A crash occurred", type: "error", buttons: ["OK"] }); + + p.then((response) => { + debug("RES:", response); + }).catch((e) => { + debug("dialog.showMessageBox CRASHED", e); + + }); + } + + } catch { + // ignore + } debug("STATE LOADED FROM FS"); debug("😍😍😍😍😍😍😍😍");