diff --git a/src/main/services/BackupManager.ts b/src/main/services/BackupManager.ts
index 2d587776..92b5ef65 100644
--- a/src/main/services/BackupManager.ts
+++ b/src/main/services/BackupManager.ts
@@ -119,10 +119,10 @@ class BackupManager {
await fs.remove(this.tempDir)
onProgress({ stage: 'completed', progress: 100, total: 100 })
- Logger.log('Backup completed successfully')
+ Logger.log('[BackupManager] Backup completed successfully')
return backupedFilePath
} catch (error) {
- Logger.error('Backup failed:', error)
+ Logger.error('[BackupManager] Backup failed:', error)
throw error
}
}
diff --git a/src/renderer/src/pages/settings/DataSettings/WebDavSettings.tsx b/src/renderer/src/pages/settings/DataSettings/WebDavSettings.tsx
index 913af6f1..e0e7487b 100644
--- a/src/renderer/src/pages/settings/DataSettings/WebDavSettings.tsx
+++ b/src/renderer/src/pages/settings/DataSettings/WebDavSettings.tsx
@@ -1,10 +1,9 @@
-import { FolderOpenOutlined, SaveOutlined, SyncOutlined } from '@ant-design/icons'
+import { FolderOpenOutlined, SaveOutlined, SyncOutlined, WarningOutlined } from '@ant-design/icons'
import { HStack } from '@renderer/components/Layout'
import { useTheme } from '@renderer/context/ThemeProvider'
-import { useRuntime } from '@renderer/hooks/useRuntime'
import { useSettings } from '@renderer/hooks/useSettings'
import { backupToWebdav, restoreFromWebdav, startAutoSync, stopAutoSync } from '@renderer/services/BackupService'
-import { useAppDispatch } from '@renderer/store'
+import { useAppDispatch, useAppSelector } from '@renderer/store'
import {
setWebdavAutoSync,
setWebdavHost as _setWebdavHost,
@@ -56,7 +55,7 @@ const WebDavSettings: FC = () => {
const { t } = useTranslation()
- const { webdavSync } = useRuntime()
+ const { webdavSync } = useAppSelector((state) => state.backup)
// 把之前备份的文件定时上传到 webdav,首先先配置 webdav 的 host, port, user, pass, path
@@ -82,16 +81,16 @@ const WebDavSettings: FC = () => {
return (
{webdavSync.syncing && }
+ {!webdavSync.syncing && webdavSync.lastSyncError && (
+
+
+
+ )}
{webdavSync.lastSyncTime && (
{t('settings.data.webdav.lastSync')}: {dayjs(webdavSync.lastSyncTime).format('HH:mm:ss')}
)}
- {webdavSync.lastSyncError && (
-
- {t('settings.data.webdav.syncError')}: {webdavSync.lastSyncError}
-
- )}
)
}
diff --git a/src/renderer/src/services/BackupService.ts b/src/renderer/src/services/BackupService.ts
index 54d85e13..e78e9e38 100644
--- a/src/renderer/src/services/BackupService.ts
+++ b/src/renderer/src/services/BackupService.ts
@@ -1,7 +1,7 @@
import db from '@renderer/databases'
import i18n from '@renderer/i18n'
import store from '@renderer/store'
-import { setWebDAVSyncState } from '@renderer/store/runtime'
+import { setWebDAVSyncState } from '@renderer/store/backup'
import dayjs from 'dayjs'
import Logger from 'electron-log'
@@ -95,25 +95,28 @@ export async function backupToWebdav({
if (success) {
store.dispatch(
setWebDAVSyncState({
- lastSyncTime: Date.now(),
lastSyncError: null
})
)
showMessage && window.message.success({ content: i18n.t('message.backup.success'), key: 'backup' })
} else {
store.dispatch(setWebDAVSyncState({ lastSyncError: 'Backup failed' }))
- showMessage && window.message.error({ content: i18n.t('message.backup.failed'), key: 'backup' })
+ window.message.error({ content: i18n.t('message.backup.failed'), key: 'backup' })
}
} catch (error: any) {
store.dispatch(setWebDAVSyncState({ lastSyncError: error.message }))
- console.error('[backup] backupToWebdav: Error uploading file to WebDAV:', error)
- showMessage &&
- window.modal.error({
- title: i18n.t('message.backup.failed'),
- content: error.message
- })
+ console.error('[Backup] backupToWebdav: Error uploading file to WebDAV:', error)
+ window.modal.error({
+ title: i18n.t('message.backup.failed'),
+ content: error.message
+ })
} finally {
- store.dispatch(setWebDAVSyncState({ syncing: false }))
+ store.dispatch(
+ setWebDAVSyncState({
+ lastSyncTime: Date.now(),
+ syncing: false
+ })
+ )
isManualBackupRunning = false
}
}
@@ -126,7 +129,7 @@ export async function restoreFromWebdav(fileName?: string) {
try {
data = await window.api.backup.restoreFromWebdav({ webdavHost, webdavUser, webdavPass, webdavPath, fileName })
} catch (error: any) {
- console.error('[backup] restoreFromWebdav: Error downloading file from WebDAV:', error)
+ console.error('[Backup] restoreFromWebdav: Error downloading file from WebDAV:', error)
window.modal.error({
title: i18n.t('message.restore.failed'),
content: error.message
@@ -136,7 +139,7 @@ export async function restoreFromWebdav(fileName?: string) {
try {
await handleData(JSON.parse(data))
} catch (error) {
- console.error('[backup] Error downloading file from WebDAV:', error)
+ console.error('[Backup] Error downloading file from WebDAV:', error)
window.message.error({ content: i18n.t('error.backup.file_format'), key: 'restore' })
}
}
@@ -171,6 +174,7 @@ export function startAutoSync() {
}
const { webdavSyncInterval } = store.getState().settings
+ const { webdavSync } = store.getState().backup
if (webdavSyncInterval <= 0) {
console.log('[AutoSync] Invalid sync interval, auto sync disabled')
@@ -178,9 +182,21 @@ export function startAutoSync() {
return
}
- syncTimeout = setTimeout(performAutoBackup, webdavSyncInterval * 60 * 1000)
+ // 用户指定的自动备份时间间隔(毫秒)
+ const requiredInterval = webdavSyncInterval * 60 * 1000
- console.log(`[AutoSync] Next sync scheduled in ${webdavSyncInterval} minutes`)
+ // 如果存在最后一次同步WebDAV的时间,以它为参考计算下一次同步的时间
+ const timeUntilNextSync = webdavSync?.lastSyncTime
+ ? Math.max(1000, webdavSync.lastSyncTime + requiredInterval - Date.now())
+ : requiredInterval
+
+ syncTimeout = setTimeout(performAutoBackup, timeUntilNextSync)
+
+ console.log(
+ `[AutoSync] Next sync scheduled in ${Math.floor(timeUntilNextSync / 1000 / 60)} minutes ${Math.floor(
+ (timeUntilNextSync / 1000) % 60
+ )} seconds`
+ )
}
async function performAutoBackup() {
@@ -192,7 +208,7 @@ export function startAutoSync() {
isAutoBackupRunning = true
try {
- console.log('[AutoSync] Performing auto backup...')
+ console.log('[AutoSync] Starting auto backup...')
await backupToWebdav({ showMessage: false })
} catch (error) {
console.error('[AutoSync] Auto backup failed:', error)
diff --git a/src/renderer/src/store/backup.ts b/src/renderer/src/store/backup.ts
new file mode 100644
index 00000000..a8b7d342
--- /dev/null
+++ b/src/renderer/src/store/backup.ts
@@ -0,0 +1,32 @@
+import { createSlice, PayloadAction } from '@reduxjs/toolkit'
+
+export interface WebDAVSyncState {
+ lastSyncTime: number | null
+ syncing: boolean
+ lastSyncError: string | null
+}
+
+export interface BackupState {
+ webdavSync: WebDAVSyncState
+}
+
+const initialState: BackupState = {
+ webdavSync: {
+ lastSyncTime: null,
+ syncing: false,
+ lastSyncError: null
+ }
+}
+
+const backupSlice = createSlice({
+ name: 'backup',
+ initialState,
+ reducers: {
+ setWebDAVSyncState: (state, action: PayloadAction>) => {
+ state.webdavSync = { ...state.webdavSync, ...action.payload }
+ }
+ }
+})
+
+export const { setWebDAVSyncState } = backupSlice.actions
+export default backupSlice.reducer
diff --git a/src/renderer/src/store/index.ts b/src/renderer/src/store/index.ts
index 0f4b5a42..9eb96906 100644
--- a/src/renderer/src/store/index.ts
+++ b/src/renderer/src/store/index.ts
@@ -5,6 +5,7 @@ import storage from 'redux-persist/lib/storage'
import agents from './agents'
import assistants from './assistants'
+import backup from './backup'
import copilot from './copilot'
import knowledge from './knowledge'
import llm from './llm'
@@ -21,6 +22,7 @@ import websearch from './websearch'
const rootReducer = combineReducers({
assistants,
agents,
+ backup,
paintings,
llm,
settings,
@@ -38,7 +40,7 @@ const persistedReducer = persistReducer(
{
key: 'cherry-studio',
storage,
- version: 81,
+ version: 82,
blacklist: ['runtime', 'messages'],
migrate
},
diff --git a/src/renderer/src/store/migrate.ts b/src/renderer/src/store/migrate.ts
index edf5f869..ccf55bca 100644
--- a/src/renderer/src/store/migrate.ts
+++ b/src/renderer/src/store/migrate.ts
@@ -772,6 +772,22 @@ const migrateConfig = {
'81': (state: RootState) => {
addProvider(state, 'copilot')
return state
+ },
+ '82': (state: RootState) => {
+ const runtimeState = state.runtime as any
+ if (runtimeState?.webdavSync) {
+ state.backup = state.backup || {}
+ state.backup = {
+ ...state.backup,
+ webdavSync: {
+ lastSyncTime: runtimeState.webdavSync.lastSyncTime || null,
+ syncing: runtimeState.webdavSync.syncing || false,
+ lastSyncError: runtimeState.webdavSync.lastSyncError || null
+ }
+ }
+ delete runtimeState.webdavSync
+ }
+ return state
}
}
diff --git a/src/renderer/src/store/runtime.ts b/src/renderer/src/store/runtime.ts
index e7962ec3..727ee182 100644
--- a/src/renderer/src/store/runtime.ts
+++ b/src/renderer/src/store/runtime.ts
@@ -11,12 +11,6 @@ export interface UpdateState {
available: boolean
}
-export interface WebDAVSyncState {
- lastSyncTime: number | null
- syncing: boolean
- lastSyncError: string | null
-}
-
export interface RuntimeState {
avatar: string
generating: boolean
@@ -25,7 +19,6 @@ export interface RuntimeState {
filesPath: string
resourcesPath: string
update: UpdateState
- webdavSync: WebDAVSyncState
export: ExportState
}
@@ -48,11 +41,6 @@ const initialState: RuntimeState = {
downloadProgress: 0,
available: false
},
- webdavSync: {
- lastSyncTime: null,
- syncing: false,
- lastSyncError: null
- },
export: {
isExporting: false
}
@@ -83,9 +71,6 @@ const runtimeSlice = createSlice({
setUpdateState: (state, action: PayloadAction>) => {
state.update = { ...state.update, ...action.payload }
},
- setWebDAVSyncState: (state, action: PayloadAction>) => {
- state.webdavSync = { ...state.webdavSync, ...action.payload }
- },
setExportState: (state, action: PayloadAction>) => {
state.export = { ...state.export, ...action.payload }
}
@@ -100,7 +85,6 @@ export const {
setFilesPath,
setResourcesPath,
setUpdateState,
- setWebDAVSyncState,
setExportState
} = runtimeSlice.actions