feat: implement maximum backups feature in WebDAV settings (#5060)
* feat: implement maximum backups feature in WebDAV settings - Add maximum backups feature to WebDAV settings Signed-off-by: ysicing <i@ysicing.me> * refactor: refactor backup file management for device-specific handling - Change the order of hostname and device type in the backup file name - Add filtering for backup files to manage device-specific backups - Update logic to delete the oldest backup files based on the specific device count Signed-off-by: ysicing <i@ysicing.me> --------- Signed-off-by: ysicing <i@ysicing.me>
This commit is contained in:
parent
3e1e814004
commit
9b21c334cc
@ -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": {
|
||||
|
||||
@ -919,7 +919,9 @@
|
||||
"syncError": "バックアップエラー",
|
||||
"syncStatus": "バックアップ状態",
|
||||
"title": "WebDAV",
|
||||
"user": "WebDAVユーザー"
|
||||
"user": "WebDAVユーザー",
|
||||
"maxBackups": "最大バックアップ数",
|
||||
"maxBackups.unlimited": "無制限"
|
||||
},
|
||||
"yuque": {
|
||||
"check": {
|
||||
|
||||
@ -919,7 +919,9 @@
|
||||
"syncError": "Ошибка резервного копирования",
|
||||
"syncStatus": "Статус резервного копирования",
|
||||
"title": "WebDAV",
|
||||
"user": "Пользователь WebDAV"
|
||||
"user": "Пользователь WebDAV",
|
||||
"maxBackups": "Максимальное количество резервных копий",
|
||||
"maxBackups.unlimited": "Без ограничений"
|
||||
},
|
||||
"yuque": {
|
||||
"check": {
|
||||
|
||||
@ -921,7 +921,9 @@
|
||||
"syncError": "备份错误",
|
||||
"syncStatus": "备份状态",
|
||||
"title": "WebDAV",
|
||||
"user": "WebDAV 用户名"
|
||||
"user": "WebDAV 用户名",
|
||||
"maxBackups": "最大备份数",
|
||||
"maxBackups.unlimited": "无限制"
|
||||
},
|
||||
"yuque": {
|
||||
"check": {
|
||||
|
||||
@ -919,7 +919,9 @@
|
||||
"syncError": "備份錯誤",
|
||||
"syncStatus": "備份狀態",
|
||||
"title": "WebDAV",
|
||||
"user": "WebDAV 使用者名稱"
|
||||
"user": "WebDAV 使用者名稱",
|
||||
"maxBackups": "最大備份數量",
|
||||
"maxBackups.unlimited": "無限制"
|
||||
},
|
||||
"yuque": {
|
||||
"check": {
|
||||
|
||||
@ -814,7 +814,9 @@
|
||||
"syncError": "Σφάλμα στην αντιγραφή ασφαλείας",
|
||||
"syncStatus": "Κατάσταση αντιγραφής ασφαλείας",
|
||||
"title": "WebDAV",
|
||||
"user": "Όνομα χρήστη WebDAV"
|
||||
"user": "Όνομα χρήστη WebDAV",
|
||||
"maxBackups": "Μέγιστο αριθμό αρχείων αντιγραφής ασφαλείας",
|
||||
"maxBackups.unlimited": "Απεριόριστο"
|
||||
},
|
||||
"yuque": {
|
||||
"check": {
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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<string | undefined>(webDAVHost)
|
||||
@ -37,6 +39,7 @@ const WebDavSettings: FC = () => {
|
||||
const [backupManagerVisible, setBackupManagerVisible] = useState(false)
|
||||
|
||||
const [syncInterval, setSyncInterval] = useState<number>(webDAVSyncInterval)
|
||||
const [maxBackups, setMaxBackups] = useState<number>(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 = () => {
|
||||
<Select.Option value={1440}>{t('settings.data.webdav.hour_interval', { count: 24 })}</Select.Option>
|
||||
</Select>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitle>{t('settings.data.webdav.maxBackups')}</SettingRowTitle>
|
||||
<Select value={maxBackups} onChange={onMaxBackupsChange} disabled={!webdavHost} style={{ width: 120 }}>
|
||||
<Select.Option value={0}>{t('settings.data.webdav.maxBackups.unlimited')}</Select.Option>
|
||||
<Select.Option value={1}>1</Select.Option>
|
||||
<Select.Option value={3}>3</Select.Option>
|
||||
<Select.Option value={5}>5</Select.Option>
|
||||
<Select.Option value={10}>10</Select.Option>
|
||||
<Select.Option value={20}>20</Select.Option>
|
||||
<Select.Option value={50}>50</Select.Option>
|
||||
</Select>
|
||||
</SettingRow>
|
||||
{webdavSync && syncInterval > 0 && (
|
||||
<>
|
||||
<SettingDivider />
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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<number>) => {
|
||||
state.webdavSyncInterval = action.payload
|
||||
},
|
||||
setWebdavMaxBackups: (state, action: PayloadAction<number>) => {
|
||||
state.webdavMaxBackups = action.payload
|
||||
},
|
||||
setCodeShowLineNumbers: (state, action: PayloadAction<boolean>) => {
|
||||
state.codeShowLineNumbers = action.payload
|
||||
},
|
||||
@ -526,6 +531,7 @@ export const {
|
||||
setWebdavPath,
|
||||
setWebdavAutoSync,
|
||||
setWebdavSyncInterval,
|
||||
setWebdavMaxBackups,
|
||||
setCodeShowLineNumbers,
|
||||
setCodeCollapsible,
|
||||
setCodeWrappable,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user