diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 2ff1ce6e..60b72808 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -919,7 +919,9 @@ "syncError": "Backup Error", "syncStatus": "Backup Status", "title": "WebDAV", - "user": "WebDAV User" + "user": "WebDAV User", + "maxBackups": "Maximum Backups", + "maxBackups.unlimited": "Unlimited" }, "yuque": { "check": { diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index 21c7db52..528f6a21 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -919,7 +919,9 @@ "syncError": "バックアップエラー", "syncStatus": "バックアップ状態", "title": "WebDAV", - "user": "WebDAVユーザー" + "user": "WebDAVユーザー", + "maxBackups": "最大バックアップ数", + "maxBackups.unlimited": "無制限" }, "yuque": { "check": { diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index ad26a716..0349c617 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -919,7 +919,9 @@ "syncError": "Ошибка резервного копирования", "syncStatus": "Статус резервного копирования", "title": "WebDAV", - "user": "Пользователь WebDAV" + "user": "Пользователь WebDAV", + "maxBackups": "Максимальное количество резервных копий", + "maxBackups.unlimited": "Без ограничений" }, "yuque": { "check": { diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 975bf2b1..86156aeb 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -921,7 +921,9 @@ "syncError": "备份错误", "syncStatus": "备份状态", "title": "WebDAV", - "user": "WebDAV 用户名" + "user": "WebDAV 用户名", + "maxBackups": "最大备份数", + "maxBackups.unlimited": "无限制" }, "yuque": { "check": { diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 5ec1ee63..72d0ba7c 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -919,7 +919,9 @@ "syncError": "備份錯誤", "syncStatus": "備份狀態", "title": "WebDAV", - "user": "WebDAV 使用者名稱" + "user": "WebDAV 使用者名稱", + "maxBackups": "最大備份數量", + "maxBackups.unlimited": "無限制" }, "yuque": { "check": { diff --git a/src/renderer/src/i18n/translate/el-gr.json b/src/renderer/src/i18n/translate/el-gr.json index db99e7c4..7ad799f4 100644 --- a/src/renderer/src/i18n/translate/el-gr.json +++ b/src/renderer/src/i18n/translate/el-gr.json @@ -814,7 +814,9 @@ "syncError": "Σφάλμα στην αντιγραφή ασφαλείας", "syncStatus": "Κατάσταση αντιγραφής ασφαλείας", "title": "WebDAV", - "user": "Όνομα χρήστη WebDAV" + "user": "Όνομα χρήστη WebDAV", + "maxBackups": "Μέγιστο αριθμό αρχείων αντιγραφής ασφαλείας", + "maxBackups.unlimited": "Απεριόριστο" }, "yuque": { "check": { diff --git a/src/renderer/src/i18n/translate/es-es.json b/src/renderer/src/i18n/translate/es-es.json index cb609f66..15f6ce99 100644 --- a/src/renderer/src/i18n/translate/es-es.json +++ b/src/renderer/src/i18n/translate/es-es.json @@ -814,7 +814,9 @@ "syncError": "Error de copia de seguridad", "syncStatus": "Estado de copia de seguridad", "title": "WebDAV", - "user": "Nombre de usuario WebDAV" + "user": "Nombre de usuario WebDAV", + "maxBackups": "Número máximo de copias de seguridad", + "maxBackups.unlimited": "Sin límite" }, "yuque": { "check": { diff --git a/src/renderer/src/i18n/translate/fr-fr.json b/src/renderer/src/i18n/translate/fr-fr.json index 51d4ee83..d4ee58ca 100644 --- a/src/renderer/src/i18n/translate/fr-fr.json +++ b/src/renderer/src/i18n/translate/fr-fr.json @@ -814,7 +814,9 @@ "syncError": "Erreur de sauvegarde", "syncStatus": "Statut de la sauvegarde", "title": "WebDAV", - "user": "Nom d'utilisateur WebDAV" + "user": "Nom d'utilisateur WebDAV", + "maxBackups": "Nombre maximal de sauvegardes", + "maxBackups.unlimited": "Illimité" }, "yuque": { "check": { diff --git a/src/renderer/src/i18n/translate/pt-pt.json b/src/renderer/src/i18n/translate/pt-pt.json index d4e9a06d..9d3ccf1a 100644 --- a/src/renderer/src/i18n/translate/pt-pt.json +++ b/src/renderer/src/i18n/translate/pt-pt.json @@ -814,7 +814,9 @@ "syncError": "Erro de backup", "syncStatus": "Status de backup", "title": "WebDAV", - "user": "Nome de usuário WebDAV" + "user": "Nome de usuário WebDAV", + "maxBackups": "Número máximo de backups", + "maxBackups.unlimited": "Sem limite" }, "yuque": { "check": { diff --git a/src/renderer/src/pages/settings/DataSettings/WebDavSettings.tsx b/src/renderer/src/pages/settings/DataSettings/WebDavSettings.tsx index 795e5522..29221f9c 100644 --- a/src/renderer/src/pages/settings/DataSettings/WebDavSettings.tsx +++ b/src/renderer/src/pages/settings/DataSettings/WebDavSettings.tsx @@ -9,6 +9,7 @@ import { useAppDispatch, useAppSelector } from '@renderer/store' import { setWebdavAutoSync, setWebdavHost as _setWebdavHost, + setWebdavMaxBackups as _setWebdavMaxBackups, setWebdavPass as _setWebdavPass, setWebdavPath as _setWebdavPath, setWebdavSyncInterval as _setWebdavSyncInterval, @@ -27,7 +28,8 @@ const WebDavSettings: FC = () => { webdavUser: webDAVUser, webdavPass: webDAVPass, webdavPath: webDAVPath, - webdavSyncInterval: webDAVSyncInterval + webdavSyncInterval: webDAVSyncInterval, + webdavMaxBackups: webDAVMaxBackups } = useSettings() const [webdavHost, setWebdavHost] = useState(webDAVHost) @@ -37,6 +39,7 @@ const WebDavSettings: FC = () => { const [backupManagerVisible, setBackupManagerVisible] = useState(false) const [syncInterval, setSyncInterval] = useState(webDAVSyncInterval) + const [maxBackups, setMaxBackups] = useState(webDAVMaxBackups) const dispatch = useAppDispatch() const { theme } = useTheme() @@ -59,6 +62,11 @@ const WebDavSettings: FC = () => { } } + const onMaxBackupsChange = (value: number) => { + setMaxBackups(value) + dispatch(_setWebdavMaxBackups(value)) + } + const renderSyncStatus = () => { if (!webdavHost) return null @@ -173,6 +181,19 @@ const WebDavSettings: FC = () => { {t('settings.data.webdav.hour_interval', { count: 24 })} + + + {t('settings.data.webdav.maxBackups')} + + {webdavSync && syncInterval > 0 && ( <> diff --git a/src/renderer/src/services/BackupService.ts b/src/renderer/src/services/BackupService.ts index 464346ba..753c5f0a 100644 --- a/src/renderer/src/services/BackupService.ts +++ b/src/renderer/src/services/BackupService.ts @@ -82,7 +82,7 @@ export async function backupToWebdav({ store.dispatch(setWebDAVSyncState({ syncing: true, lastSyncError: null })) - const { webdavHost, webdavUser, webdavPass, webdavPath } = store.getState().settings + const { webdavHost, webdavUser, webdavPass, webdavPath, webdavMaxBackups } = store.getState().settings let deviceType = 'unknown' let hostname = 'unknown' try { @@ -92,7 +92,7 @@ export async function backupToWebdav({ Logger.error('[Backup] Failed to get device type or hostname:', error) } const timestamp = dayjs().format('YYYYMMDDHHmmss') - const backupFileName = customFileName || `cherry-studio.${timestamp}.${deviceType}.${hostname}.zip` + const backupFileName = customFileName || `cherry-studio.${timestamp}.${hostname}.${deviceType}.zip` const finalFileName = backupFileName.endsWith('.zip') ? backupFileName : `${backupFileName}.zip` const backupData = await getBackupData() @@ -114,6 +114,47 @@ export async function backupToWebdav({ if (showMessage && !autoBackupProcess) { window.message.success({ content: i18n.t('message.backup.success'), key: 'backup' }) } + + // 清理旧备份文件 + if (webdavMaxBackups > 0) { + try { + // 获取所有备份文件 + const files = await window.api.backup.listWebdavFiles({ + webdavHost, + webdavUser, + webdavPass, + webdavPath + }) + + // 筛选当前设备的备份文件 + const currentDeviceFiles = files.filter((file) => { + // 检查文件名是否包含当前设备的标识信息 + return file.fileName.includes(deviceType) && file.fileName.includes(hostname) + }) + + // 如果当前设备的备份文件数量超过最大保留数量,删除最旧的文件 + if (currentDeviceFiles.length > webdavMaxBackups) { + // 文件已按修改时间降序排序,所以最旧的文件在末尾 + const filesToDelete = currentDeviceFiles.slice(webdavMaxBackups) + + for (const file of filesToDelete) { + try { + await window.api.backup.deleteWebdavFile(file.fileName, { + webdavHost, + webdavUser, + webdavPass, + webdavPath + }) + Logger.log(`[Backup] Deleted old backup file: ${file.fileName}`) + } catch (error) { + Logger.error(`[Backup] Failed to delete old backup file: ${file.fileName}`, error) + } + } + } + } catch (error) { + Logger.error('[Backup] Failed to clean up old backup files:', error) + } + } } else { // if auto backup process, throw error if (autoBackupProcess) { diff --git a/src/renderer/src/store/settings.ts b/src/renderer/src/store/settings.ts index 6ad8cfbc..f153ff69 100644 --- a/src/renderer/src/store/settings.ts +++ b/src/renderer/src/store/settings.ts @@ -72,6 +72,7 @@ export interface SettingsState { webdavPath: string webdavAutoSync: boolean webdavSyncInterval: number + webdavMaxBackups: number translateModelPrompt: string autoTranslateWithSpace: boolean enableTopicNaming: boolean @@ -176,6 +177,7 @@ export const initialState: SettingsState = { webdavPath: '/cherry-studio', webdavAutoSync: false, webdavSyncInterval: 0, + webdavMaxBackups: 0, translateModelPrompt: TRANSLATE_PROMPT, autoTranslateWithSpace: false, enableTopicNaming: true, @@ -334,6 +336,9 @@ const settingsSlice = createSlice({ setWebdavSyncInterval: (state, action: PayloadAction) => { state.webdavSyncInterval = action.payload }, + setWebdavMaxBackups: (state, action: PayloadAction) => { + state.webdavMaxBackups = action.payload + }, setCodeShowLineNumbers: (state, action: PayloadAction) => { state.codeShowLineNumbers = action.payload }, @@ -526,6 +531,7 @@ export const { setWebdavPath, setWebdavAutoSync, setWebdavSyncInterval, + setWebdavMaxBackups, setCodeShowLineNumbers, setCodeCollapsible, setCodeWrappable,