mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2025-12-26 20:50:30 +08:00
feat: add webdav max backups configuration and cleanup logic (#862)
issue #648.
This commit is contained in:
parent
827e744601
commit
674cefcc29
@ -19,7 +19,8 @@ export async function webdavBackup(): Promise<boolean> {
|
|||||||
webdavUrl = '',
|
webdavUrl = '',
|
||||||
webdavUsername = '',
|
webdavUsername = '',
|
||||||
webdavPassword = '',
|
webdavPassword = '',
|
||||||
webdavDir = 'mihomo-party'
|
webdavDir = 'mihomo-party',
|
||||||
|
webdavMaxBackups = 0
|
||||||
} = await getAppConfig()
|
} = await getAppConfig()
|
||||||
const zip = new AdmZip()
|
const zip = new AdmZip()
|
||||||
|
|
||||||
@ -44,7 +45,41 @@ export async function webdavBackup(): Promise<boolean> {
|
|||||||
// ignore
|
// 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<void> {
|
export async function webdavRestore(filename: string): Promise<void> {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import SettingCard from '../base/base-setting-card'
|
import SettingCard from '../base/base-setting-card'
|
||||||
import SettingItem from '../base/base-setting-item'
|
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 { listWebdavBackups, webdavBackup } from '@renderer/utils/ipc'
|
||||||
import WebdavRestoreModal from './webdav-restore-modal'
|
import WebdavRestoreModal from './webdav-restore-modal'
|
||||||
import debounce from '@renderer/utils/debounce'
|
import debounce from '@renderer/utils/debounce'
|
||||||
@ -11,16 +11,31 @@ import { useTranslation } from 'react-i18next'
|
|||||||
const WebdavConfig: React.FC = () => {
|
const WebdavConfig: React.FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { appConfig, patchAppConfig } = useAppConfig()
|
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 [backuping, setBackuping] = useState(false)
|
||||||
const [restoring, setRestoring] = useState(false)
|
const [restoring, setRestoring] = useState(false)
|
||||||
const [filenames, setFilenames] = useState<string[]>([])
|
const [filenames, setFilenames] = useState<string[]>([])
|
||||||
const [restoreOpen, setRestoreOpen] = useState(false)
|
const [restoreOpen, setRestoreOpen] = useState(false)
|
||||||
|
|
||||||
const [webdav, setWebdav] = useState({ webdavUrl, webdavUsername, webdavPassword, webdavDir })
|
const [webdav, setWebdav] = useState({
|
||||||
const setWebdavDebounce = debounce(({ webdavUrl, webdavUsername, webdavPassword, webdavDir }) => {
|
webdavUrl,
|
||||||
patchAppConfig({ webdavUrl, webdavUsername, webdavPassword, webdavDir })
|
webdavUsername,
|
||||||
}, 500)
|
webdavPassword,
|
||||||
|
webdavDir,
|
||||||
|
webdavMaxBackups
|
||||||
|
})
|
||||||
|
const setWebdavDebounce = debounce(
|
||||||
|
({ webdavUrl, webdavUsername, webdavPassword, webdavDir, webdavMaxBackups }) => {
|
||||||
|
patchAppConfig({ webdavUrl, webdavUsername, webdavPassword, webdavDir, webdavMaxBackups })
|
||||||
|
},
|
||||||
|
500
|
||||||
|
)
|
||||||
const handleBackup = async (): Promise<void> => {
|
const handleBackup = async (): Promise<void> => {
|
||||||
setBackuping(true)
|
setBackuping(true)
|
||||||
try {
|
try {
|
||||||
@ -98,6 +113,28 @@ const WebdavConfig: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
<SettingItem title={t('webdav.maxBackups')} divider>
|
||||||
|
<Select
|
||||||
|
classNames={{ trigger: 'data-[hover=true]:bg-default-200' }}
|
||||||
|
className="w-[150px]"
|
||||||
|
size="sm"
|
||||||
|
selectedKeys={new Set([webdav.webdavMaxBackups.toString()])}
|
||||||
|
aria-label={t('webdav.maxBackups')}
|
||||||
|
onSelectionChange={(v) => {
|
||||||
|
const value = Number.parseInt(Array.from(v)[0] as string, 10)
|
||||||
|
setWebdav({ ...webdav, webdavMaxBackups: value })
|
||||||
|
setWebdavDebounce({ ...webdav, webdavMaxBackups: value })
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectItem key="0">{t('webdav.noLimit')}</SelectItem>
|
||||||
|
<SelectItem key="1">1</SelectItem>
|
||||||
|
<SelectItem key="3">3</SelectItem>
|
||||||
|
<SelectItem key="5">5</SelectItem>
|
||||||
|
<SelectItem key="10">10</SelectItem>
|
||||||
|
<SelectItem key="15">15</SelectItem>
|
||||||
|
<SelectItem key="20">20</SelectItem>
|
||||||
|
</Select>
|
||||||
|
</SettingItem>
|
||||||
<div className="flex justify0between">
|
<div className="flex justify0between">
|
||||||
<Button isLoading={backuping} fullWidth size="sm" className="mr-1" onPress={handleBackup}>
|
<Button isLoading={backuping} fullWidth size="sm" className="mr-1" onPress={handleBackup}>
|
||||||
{t('webdav.backup')}
|
{t('webdav.backup')}
|
||||||
|
|||||||
@ -179,6 +179,8 @@
|
|||||||
"webdav.dir": "WebDAV Backup Directory",
|
"webdav.dir": "WebDAV Backup Directory",
|
||||||
"webdav.username": "WebDAV Username",
|
"webdav.username": "WebDAV Username",
|
||||||
"webdav.password": "WebDAV Password",
|
"webdav.password": "WebDAV Password",
|
||||||
|
"webdav.maxBackups": "Max Backups",
|
||||||
|
"webdav.noLimit": "No Limit",
|
||||||
"webdav.backup": "Backup",
|
"webdav.backup": "Backup",
|
||||||
"webdav.restore.title": "Restore Backup",
|
"webdav.restore.title": "Restore Backup",
|
||||||
"webdav.restore.noBackups": "No backups available",
|
"webdav.restore.noBackups": "No backups available",
|
||||||
|
|||||||
@ -179,6 +179,8 @@
|
|||||||
"webdav.dir": "پوشه پشتیبانگیری WebDAV",
|
"webdav.dir": "پوشه پشتیبانگیری WebDAV",
|
||||||
"webdav.username": "نام کاربری WebDAV",
|
"webdav.username": "نام کاربری WebDAV",
|
||||||
"webdav.password": "رمز عبور WebDAV",
|
"webdav.password": "رمز عبور WebDAV",
|
||||||
|
"webdav.maxBackups": "حداکثر نسخه پشتیبان",
|
||||||
|
"webdav.noLimit": "بدون محدودیت",
|
||||||
"webdav.backup": "پشتیبانگیری",
|
"webdav.backup": "پشتیبانگیری",
|
||||||
"webdav.restore.title": "بازیابی پشتیبان",
|
"webdav.restore.title": "بازیابی پشتیبان",
|
||||||
"webdav.restore.noBackups": "هیچ پشتیبانی موجود نیست",
|
"webdav.restore.noBackups": "هیچ پشتیبانی موجود نیست",
|
||||||
|
|||||||
@ -179,6 +179,8 @@
|
|||||||
"webdav.dir": "Каталог резервных копий WebDAV",
|
"webdav.dir": "Каталог резервных копий WebDAV",
|
||||||
"webdav.username": "Имя пользователя WebDAV",
|
"webdav.username": "Имя пользователя WebDAV",
|
||||||
"webdav.password": "Пароль WebDAV",
|
"webdav.password": "Пароль WebDAV",
|
||||||
|
"webdav.maxBackups": "Максимум резервных копий",
|
||||||
|
"webdav.noLimit": "Без ограничений",
|
||||||
"webdav.backup": "Резервное копирование",
|
"webdav.backup": "Резервное копирование",
|
||||||
"webdav.restore.title": "Восстановление резервной копии",
|
"webdav.restore.title": "Восстановление резервной копии",
|
||||||
"webdav.restore.noBackups": "Нет доступных резервных копий",
|
"webdav.restore.noBackups": "Нет доступных резервных копий",
|
||||||
|
|||||||
@ -179,6 +179,8 @@
|
|||||||
"webdav.dir": "WebDAV 备份目录",
|
"webdav.dir": "WebDAV 备份目录",
|
||||||
"webdav.username": "WebDAV 用户名",
|
"webdav.username": "WebDAV 用户名",
|
||||||
"webdav.password": "WebDAV 密码",
|
"webdav.password": "WebDAV 密码",
|
||||||
|
"webdav.maxBackups": "最大备份数",
|
||||||
|
"webdav.noLimit": "不限制",
|
||||||
"webdav.backup": "备份",
|
"webdav.backup": "备份",
|
||||||
"webdav.restore.title": "恢复备份",
|
"webdav.restore.title": "恢复备份",
|
||||||
"webdav.restore.noBackups": "还没有备份",
|
"webdav.restore.noBackups": "还没有备份",
|
||||||
|
|||||||
1
src/shared/types.d.ts
vendored
1
src/shared/types.d.ts
vendored
@ -284,6 +284,7 @@ interface IAppConfig {
|
|||||||
webdavDir?: string
|
webdavDir?: string
|
||||||
webdavUsername?: string
|
webdavUsername?: string
|
||||||
webdavPassword?: string
|
webdavPassword?: string
|
||||||
|
webdavMaxBackups?: number
|
||||||
useNameserverPolicy: boolean
|
useNameserverPolicy: boolean
|
||||||
nameserverPolicy: { [key: string]: string | string[] }
|
nameserverPolicy: { [key: string]: string | string[] }
|
||||||
showWindowShortcut?: string
|
showWindowShortcut?: string
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user