diff --git a/src/main/resolve/backup.ts b/src/main/resolve/backup.ts index c0a7973..b6fdb94 100644 --- a/src/main/resolve/backup.ts +++ b/src/main/resolve/backup.ts @@ -19,7 +19,8 @@ export async function webdavBackup(): Promise { webdavUrl = '', webdavUsername = '', webdavPassword = '', - webdavDir = 'mihomo-party' + webdavDir = 'mihomo-party', + webdavMaxBackups = 0 } = await getAppConfig() const zip = new AdmZip() @@ -44,7 +45,41 @@ export async function webdavBackup(): Promise { // ignore } - return await client.putFileContents(`${webdavDir}/${zipFileName}`, zip.toBuffer()) + const result = await client.putFileContents(`${webdavDir}/${zipFileName}`, zip.toBuffer()) + + if (webdavMaxBackups > 0) { + try { + const files = await client.getDirectoryContents(webdavDir, { glob: '*.zip' }) + const fileList = Array.isArray(files) ? files : files.data + + const currentPlatformFiles = fileList.filter((file) => { + return file.basename.startsWith(`${process.platform}_`) + }) + + currentPlatformFiles.sort((a, b) => { + const timeA = a.basename.match(/_(\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2})\.zip$/)?.[1] || '' + const timeB = b.basename.match(/_(\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2})\.zip$/)?.[1] || '' + return timeB.localeCompare(timeA) + }) + + if (currentPlatformFiles.length > webdavMaxBackups) { + const filesToDelete = currentPlatformFiles.slice(webdavMaxBackups) + + for (let i = 0; i < filesToDelete.length; i++) { + const file = filesToDelete[i] + await client.deleteFile(`${webdavDir}/${file.basename}`) + + if (i < filesToDelete.length - 1) { + await new Promise((resolve) => setTimeout(resolve, 500)) + } + } + } + } catch (error) { + console.error('Failed to clean up old backup files:', error) + } + } + + return result } export async function webdavRestore(filename: string): Promise { diff --git a/src/renderer/src/components/settings/webdav-config.tsx b/src/renderer/src/components/settings/webdav-config.tsx index 4480be4..820c5bd 100644 --- a/src/renderer/src/components/settings/webdav-config.tsx +++ b/src/renderer/src/components/settings/webdav-config.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react' import SettingCard from '../base/base-setting-card' import SettingItem from '../base/base-setting-item' -import { Button, Input } from '@heroui/react' +import { Button, Input, Select, SelectItem } from '@heroui/react' import { listWebdavBackups, webdavBackup } from '@renderer/utils/ipc' import WebdavRestoreModal from './webdav-restore-modal' import debounce from '@renderer/utils/debounce' @@ -11,16 +11,31 @@ import { useTranslation } from 'react-i18next' const WebdavConfig: React.FC = () => { const { t } = useTranslation() const { appConfig, patchAppConfig } = useAppConfig() - const { webdavUrl, webdavUsername, webdavPassword, webdavDir = 'mihomo-party' } = appConfig || {} + const { + webdavUrl, + webdavUsername, + webdavPassword, + webdavDir = 'mihomo-party', + webdavMaxBackups = 0 + } = appConfig || {} const [backuping, setBackuping] = useState(false) const [restoring, setRestoring] = useState(false) const [filenames, setFilenames] = useState([]) const [restoreOpen, setRestoreOpen] = useState(false) - const [webdav, setWebdav] = useState({ webdavUrl, webdavUsername, webdavPassword, webdavDir }) - const setWebdavDebounce = debounce(({ webdavUrl, webdavUsername, webdavPassword, webdavDir }) => { - patchAppConfig({ webdavUrl, webdavUsername, webdavPassword, webdavDir }) - }, 500) + const [webdav, setWebdav] = useState({ + webdavUrl, + webdavUsername, + webdavPassword, + webdavDir, + webdavMaxBackups + }) + const setWebdavDebounce = debounce( + ({ webdavUrl, webdavUsername, webdavPassword, webdavDir, webdavMaxBackups }) => { + patchAppConfig({ webdavUrl, webdavUsername, webdavPassword, webdavDir, webdavMaxBackups }) + }, + 500 + ) const handleBackup = async (): Promise => { setBackuping(true) try { @@ -98,6 +113,28 @@ const WebdavConfig: React.FC = () => { }} /> + + +