From 488a01d7d79bce26c2f82d9797e0495ad80bc71d Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Fri, 1 Aug 2025 14:47:11 +0800 Subject: [PATCH] fix: flush redux persist data when app quit and update (#8741) * feat(database): enable strict transaction durability for CherryStudio database - Updated the Dexie database initialization to include `chromeTransactionDurability: 'strict'`, enhancing data integrity during transactions. * feat(app): enhance application shutdown process and data flushing - Added functionality to flush storage data and cookies before quitting the application, ensuring data integrity. - Introduced a new `handleBeforeQuit` function to centralize cleanup logic for both manual and update-triggered quits. - Updated logging to provide better insights during the shutdown process. - Modified ProxyManager to use debug level for unchanged proxy configurations. - Added `persistor` to the global window object and implemented `handleSaveData` to flush Redux state before quitting. * format code * feat(ipc): add App_SaveData channel and implement data saving on window close - Introduced a new IPC channel `App_SaveData` for saving application data. - Updated `WindowService` to send a save data message when the main window is closed. - Enhanced `useAppInit` hook to handle the `App_SaveData` event and trigger data saving logic. * refactor(env): remove persistor from global window object - Removed the `persistor` property from the global `window` object in both `env.d.ts` and `index.ts` files, streamlining the application state management. --- packages/shared/IpcChannel.ts | 1 + src/main/services/ProxyManager.ts | 2 +- src/main/services/WindowService.ts | 7 +++++++ src/renderer/src/hooks/useAppInit.ts | 8 ++++++++ src/renderer/src/pages/settings/AboutSettings.tsx | 3 ++- src/renderer/src/store/index.ts | 9 +++++++++ 6 files changed, 28 insertions(+), 2 deletions(-) diff --git a/packages/shared/IpcChannel.ts b/packages/shared/IpcChannel.ts index 42ff62db2a..715b5b6d26 100644 --- a/packages/shared/IpcChannel.ts +++ b/packages/shared/IpcChannel.ts @@ -34,6 +34,7 @@ export enum IpcChannel { App_InstallUvBinary = 'app:install-uv-binary', App_InstallBunBinary = 'app:install-bun-binary', App_LogToMain = 'app:log-to-main', + App_SaveData = 'app:save-data', App_MacIsProcessTrusted = 'app:mac-is-process-trusted', App_MacRequestProcessTrust = 'app:mac-request-process-trust', diff --git a/src/main/services/ProxyManager.ts b/src/main/services/ProxyManager.ts index 1374b87762..b6b68ff2ec 100644 --- a/src/main/services/ProxyManager.ts +++ b/src/main/services/ProxyManager.ts @@ -66,7 +66,7 @@ export class ProxyManager { try { if (config?.mode === this.config?.mode && config?.proxyRules === this.config?.proxyRules) { - logger.info('proxy config is the same, skip configure') + logger.debug('proxy config is the same, skip configure') return } diff --git a/src/main/services/WindowService.ts b/src/main/services/WindowService.ts index c9912b9d04..64667cf618 100644 --- a/src/main/services/WindowService.ts +++ b/src/main/services/WindowService.ts @@ -319,6 +319,13 @@ export class WindowService { private setupWindowLifecycleEvents(mainWindow: BrowserWindow) { mainWindow.on('close', (event) => { + // save data before when close window + try { + mainWindow.webContents.send(IpcChannel.App_SaveData) + } catch (error) { + logger.error('Failed to save data:', error as Error) + } + // 如果已经触发退出,直接退出 if (app.isQuitting) { return app.quit() diff --git a/src/renderer/src/hooks/useAppInit.ts b/src/renderer/src/hooks/useAppInit.ts index 60b8ce448d..e1f1aebf5e 100644 --- a/src/renderer/src/hooks/useAppInit.ts +++ b/src/renderer/src/hooks/useAppInit.ts @@ -8,10 +8,12 @@ import KnowledgeQueue from '@renderer/queue/KnowledgeQueue' import MemoryService from '@renderer/services/MemoryService' import { useAppDispatch } from '@renderer/store' import { useAppSelector } from '@renderer/store' +import { handleSaveData } from '@renderer/store' import { selectMemoryConfig } from '@renderer/store/memory' import { setAvatar, setFilesPath, setResourcesPath, setUpdateState } from '@renderer/store/runtime' import { delay, runAsyncFunction } from '@renderer/utils' import { defaultLanguage } from '@shared/config/constant' +import { IpcChannel } from '@shared/IpcChannel' import { useLiveQuery } from 'dexie-react-hooks' import { useEffect } from 'react' @@ -49,6 +51,12 @@ export function useAppInit() { }) }, []) + useEffect(() => { + window.electron.ipcRenderer.on(IpcChannel.App_SaveData, async () => { + await handleSaveData() + }) + }, []) + useUpdateHandler() useFullScreenNotice() diff --git a/src/renderer/src/pages/settings/AboutSettings.tsx b/src/renderer/src/pages/settings/AboutSettings.tsx index 5ebc2ae4a9..72a545f4b7 100644 --- a/src/renderer/src/pages/settings/AboutSettings.tsx +++ b/src/renderer/src/pages/settings/AboutSettings.tsx @@ -7,7 +7,7 @@ import { useMinappPopup } from '@renderer/hooks/useMinappPopup' import { useRuntime } from '@renderer/hooks/useRuntime' import { useSettings } from '@renderer/hooks/useSettings' import i18n from '@renderer/i18n' -import { useAppDispatch } from '@renderer/store' +import { handleSaveData, useAppDispatch } from '@renderer/store' import { setUpdateState } from '@renderer/store/runtime' import { ThemeMode } from '@renderer/types' import { runAsyncFunction } from '@renderer/utils' @@ -41,6 +41,7 @@ const AboutSettings: FC = () => { } if (update.downloaded) { + await handleSaveData() window.api.showUpdateDialog() return } diff --git a/src/renderer/src/store/index.ts b/src/renderer/src/store/index.ts index a2e0d3813a..f0912b24a3 100644 --- a/src/renderer/src/store/index.ts +++ b/src/renderer/src/store/index.ts @@ -1,4 +1,5 @@ import { combineReducers, configureStore } from '@reduxjs/toolkit' +import { loggerService } from '@renderer/services/LoggerService' import { useDispatch, useSelector, useStore } from 'react-redux' import { FLUSH, PAUSE, PERSIST, persistReducer, persistStore, PURGE, REGISTER, REHYDRATE } from 'redux-persist' import storage from 'redux-persist/lib/storage' @@ -28,6 +29,8 @@ import tabs from './tabs' import translate from './translate' import websearch from './websearch' +const logger = loggerService.withContext('Store') + const rootReducer = combineReducers({ assistants, agents, @@ -101,4 +104,10 @@ export const useAppSelector = useSelector.withTypes() export const useAppStore = useStore.withTypes() window.store = store +export async function handleSaveData() { + logger.info('Flushing redux persistor data') + await persistor.flush() + logger.info('Flushed redux persistor data') +} + export default store