feat(settings): add single-file overwrite options and sync maxBackups

This commit is contained in:
GeorgeDong32 2025-10-21 22:45:44 +08:00 committed by GeorgeDong32
parent f846a27418
commit 980f20fcca
7 changed files with 348 additions and 130 deletions

View File

@ -2916,7 +2916,7 @@
"singleFileName": {
"title": "自定义文件名(可选)",
"placeholder": "如cherry-studio.<hostname>.<device>.zip",
"help": "留空将使用默认格式cherry-studio.[主机名].[设备类型].zip\n支持的变量:{hostname} - 主机名,{device} - 设备类型\n不支持的字符:<>:\"/\\|?*\n最大长度250个字符",
"help": "留空将使用默认格式cherry-studio.[主机名].[设备类型].zip\n支持的变量{hostname} - 主机名,{device} - 设备类型\n不支持的字符<>:\"/\\|?*\n最大长度250个字符",
"invalid_chars": "文件名包含无效字符",
"reserved": "文件名是系统保留名称",
"too_long": "文件名过长"

View File

@ -69,6 +69,11 @@ const LocalBackupSettings: React.FC = () => {
const { localBackupSync } = useAppSelector((state) => state.backup)
// 同步 maxBackups 状态
useEffect(() => {
setMaxBackups(localBackupMaxBackupsSetting)
}, [localBackupMaxBackupsSetting])
const onSyncIntervalChange = (value: number) => {
setSyncInterval(value)
dispatch(_setLocalBackupSyncInterval(value))
@ -273,37 +278,6 @@ const LocalBackupSettings: React.FC = () => {
<SettingGroup theme={theme}>
<SettingTitle>{t('settings.data.local.title')}</SettingTitle>
<SettingDivider />
{/* 覆盖式单文件备份,仅在自动备份开启且保留份数=1时推荐启用 */}
<SettingRow>
<SettingRowTitle>
{t('settings.data.backup.singleFileOverwrite.title') || '覆盖式单文件备份(同名覆盖)'}
</SettingRowTitle>
<Switch
checked={localSingleFileOverwrite}
onChange={onSingleFileOverwriteChange}
disabled={!(syncInterval > 0 && maxBackups === 1)}
/>
</SettingRow>
<SettingRow>
<SettingHelpText>
{t('settings.data.backup.singleFileOverwrite.help') ||
'当自动备份开启且保留份数为1时使用固定文件名每次覆盖。'}
</SettingHelpText>
</SettingRow>
<SettingRow>
<SettingRowTitle>{t('settings.data.backup.singleFileName.title') || '自定义文件名(可选)'}</SettingRowTitle>
<Input
placeholder={
t('settings.data.backup.singleFileName.placeholder') || '如cherry-studio.<hostname>.<device>.zip'
}
value={localSingleFileName}
onChange={(e) => onSingleFileNameChange(e.target.value)}
onBlur={onSingleFileNameBlur}
style={{ width: 300 }}
disabled={!localSingleFileOverwrite || !(syncInterval > 0 && maxBackups === 1)}
/>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.local.directory.label')}</SettingRowTitle>
<HStack gap="5px">
@ -383,6 +357,58 @@ const LocalBackupSettings: React.FC = () => {
<SettingRow>
<SettingHelpText>{t('settings.data.backup.skip_file_data_help')}</SettingHelpText>
</SettingRow>
{/* 覆盖式单文件备份,仅在自动备份开启且保留份数=1时推荐启用 */}
<SettingDivider />
<SettingRow>
<SettingRowTitle>
{t('settings.data.backup.singleFileOverwrite.title') || '覆盖式单文件备份(同名覆盖)'}
</SettingRowTitle>
<Switch
checked={localSingleFileOverwrite}
onChange={onSingleFileOverwriteChange}
disabled={!(syncInterval > 0 && maxBackups === 1)}
/>
</SettingRow>
<SettingRow>
<SettingHelpText>
{t('settings.data.backup.singleFileOverwrite.help') || (
<div>
<p>1使</p>
<p style={{ marginTop: 8, fontSize: 12, color: 'var(--text-secondary)' }}>
</p>
</div>
)}
</SettingHelpText>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.backup.singleFileName.title') || '自定义文件名(可选)'}</SettingRowTitle>
<Input
placeholder={
t('settings.data.backup.singleFileName.placeholder') || '如cherry-studio.<hostname>.<device>.zip'
}
value={localSingleFileName}
onChange={(e) => onSingleFileNameChange(e.target.value)}
onBlur={onSingleFileNameBlur}
style={{ width: 300 }}
disabled={!localSingleFileOverwrite || !(syncInterval > 0 && maxBackups === 1)}
/>
</SettingRow>
<SettingRow>
<SettingHelpText>
{t('settings.data.backup.singleFileName.help') || (
<div style={{ fontSize: 12, color: 'var(--text-secondary)' }}>
<p> 使cherry-studio.[].[].zip</p>
<p>
{`{hostname}`} - {`{device}`} -
</p>
<p> {'<>:"/\\|?*'}</p>
<p> 250</p>
</div>
)}
</SettingHelpText>
</SettingRow>
{localBackupSync && syncInterval > 0 && (
<>
<SettingDivider />

View File

@ -20,6 +20,8 @@ import {
setNutstoreAutoSync,
setNutstoreMaxBackups,
setNutstorePath,
setNutstoreSingleFileName,
setNutstoreSingleFileOverwrite,
setNutstoreSkipBackupFile,
setNutstoreSyncInterval,
setNutstoreToken
@ -44,7 +46,9 @@ const NutstoreSettings: FC = () => {
nutstoreAutoSync,
nutstoreSyncState,
nutstoreSkipBackupFile,
nutstoreMaxBackups
nutstoreMaxBackups,
nutstoreSingleFileOverwrite,
nutstoreSingleFileName
} = useAppSelector((state) => state.nutstore)
const dispatch = useAppDispatch()
@ -55,12 +59,20 @@ const NutstoreSettings: FC = () => {
const [checkConnectionLoading, setCheckConnectionLoading] = useState(false)
const [nsConnected, setNsConnected] = useState<boolean>(false)
const [syncInterval, setSyncInterval] = useState<number>(nutstoreSyncInterval)
const [maxBackups, setMaxBackups] = useState<number>(nutstoreMaxBackups)
const [nutSkipBackupFile, setNutSkipBackupFile] = useState<boolean>(nutstoreSkipBackupFile)
const [nutSingleFileOverwrite, setNutSingleFileOverwrite] = useState<boolean>(nutstoreSingleFileOverwrite ?? false)
const [nutSingleFileName, setNutSingleFileName] = useState<string>(nutstoreSingleFileName ?? '')
const [backupManagerVisible, setBackupManagerVisible] = useState(false)
const nutstoreSSOHandler = useNutstoreSSO()
const { setTimeoutTimer } = useTimer()
// 同步 maxBackups 状态
useEffect(() => {
setMaxBackups(nutstoreMaxBackups)
}, [nutstoreMaxBackups])
const handleClickNutstoreSSO = useCallback(async () => {
const ssoUrl = await window.api.nutstore.getSSOUrl()
window.open(ssoUrl, '_blank')
@ -142,9 +154,72 @@ const NutstoreSettings: FC = () => {
}
const onMaxBackupsChange = (value: number) => {
setMaxBackups(value)
dispatch(setNutstoreMaxBackups(value))
}
const onSingleFileOverwriteChange = (value: boolean) => {
// Only show confirmation when enabling
if (value && !nutSingleFileOverwrite) {
window.modal.confirm({
title: t('settings.data.backup.singleFileOverwrite.confirm.title') || '启用覆盖式备份',
content: (
<div>
<p>{t('settings.data.backup.singleFileOverwrite.confirm.content1') || '启用后,自动备份将:'}</p>
<ul style={{ marginLeft: 20, marginTop: 10 }}>
<li>{t('settings.data.backup.singleFileOverwrite.confirm.item1') || '使用固定文件名,不再添加时间戳'}</li>
<li>{t('settings.data.backup.singleFileOverwrite.confirm.item2') || '每次备份都会覆盖同名文件'}</li>
<li>{t('settings.data.backup.singleFileOverwrite.confirm.item3') || '仅保留最新的一个备份文件'}</li>
</ul>
<p style={{ marginTop: 10, color: 'var(--text-secondary)' }}>
{t('settings.data.backup.singleFileOverwrite.confirm.note') ||
'注意此设置仅在自动备份且保留份数为1时生效'}
</p>
</div>
),
okText: t('common.confirm') || '确认',
cancelText: t('common.cancel') || '取消',
onOk: () => {
setNutSingleFileOverwrite(value)
dispatch(setNutstoreSingleFileOverwrite(value))
}
})
} else {
setNutSingleFileOverwrite(value)
dispatch(setNutstoreSingleFileOverwrite(value))
}
}
const onSingleFileNameChange = (value: string) => {
setNutSingleFileName(value)
}
const onSingleFileNameBlur = () => {
const trimmed = nutSingleFileName.trim()
// Validate filename
if (trimmed) {
// Check for invalid characters
const invalidChars = /[<>:"/\\|?*]/
if (invalidChars.test(trimmed)) {
window.toast.error(t('settings.data.backup.singleFileName.invalid_chars') || '文件名包含无效字符')
return
}
// Check for reserved names (Windows)
const reservedNames = /^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/i
const nameWithoutExt = trimmed.replace(/\.zip$/i, '')
if (reservedNames.test(nameWithoutExt)) {
window.toast.error(t('settings.data.backup.singleFileName.reserved') || '文件名是系统保留名称')
return
}
// Check length
if (trimmed.length > 250) {
window.toast.error(t('settings.data.backup.singleFileName.too_long') || '文件名过长')
return
}
}
dispatch(setNutstoreSingleFileName(trimmed))
}
const handleClickPathChange = async () => {
if (!nutstoreToken) {
return
@ -336,6 +411,60 @@ const NutstoreSettings: FC = () => {
<SettingRow>
<SettingHelpText>{t('settings.data.backup.skip_file_data_help')}</SettingHelpText>
</SettingRow>
{/* 覆盖式单文件备份,仅在自动备份开启且保留份数=1时推荐启用 */}
<SettingDivider />
<SettingRow>
<SettingRowTitle>
{t('settings.data.backup.singleFileOverwrite.title') || '覆盖式单文件备份(同名覆盖)'}
</SettingRowTitle>
<Switch
checked={nutSingleFileOverwrite}
onChange={onSingleFileOverwriteChange}
disabled={!(syncInterval > 0 && maxBackups === 1)}
/>
</SettingRow>
<SettingRow>
<SettingHelpText>
{t('settings.data.backup.singleFileOverwrite.help') || (
<div>
<p>1使</p>
<p style={{ marginTop: 8, fontSize: 12, color: 'var(--text-secondary)' }}>
</p>
</div>
)}
</SettingHelpText>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>
{t('settings.data.backup.singleFileName.title') || '自定义文件名(可选)'}
</SettingRowTitle>
<Input
placeholder={
t('settings.data.backup.singleFileName.placeholder') || '如cherry-studio.<hostname>.<device>.zip'
}
value={nutSingleFileName}
onChange={(e) => onSingleFileNameChange(e.target.value)}
onBlur={onSingleFileNameBlur}
style={{ width: 300 }}
disabled={!nutSingleFileOverwrite || !(syncInterval > 0 && maxBackups === 1)}
/>
</SettingRow>
<SettingRow>
<SettingHelpText>
{t('settings.data.backup.singleFileName.help') || (
<div style={{ fontSize: 12, color: 'var(--text-secondary)' }}>
<p> 使cherry-studio.[].[].zip</p>
<p>
{`{hostname}`} - {`{device}`} -
</p>
<p> {'<>:"/\\|?*'}</p>
<p> 250</p>
</div>
)}
</SettingHelpText>
</SettingRow>
</>
)}
<>

View File

@ -13,7 +13,7 @@ import { setS3Partial } from '@renderer/store/settings'
import { S3Config } from '@renderer/types'
import { Button, Input, Switch, Tooltip } from 'antd'
import dayjs from 'dayjs'
import { FC, useState } from 'react'
import { FC, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { SettingDivider, SettingGroup, SettingHelpText, SettingRow, SettingRowTitle, SettingTitle } from '..'
@ -56,6 +56,11 @@ const S3Settings: FC = () => {
const { s3Sync } = useAppSelector((state) => state.backup)
// 同步 maxBackups 状态
useEffect(() => {
setMaxBackups(s3MaxBackupsInit)
}, [s3MaxBackupsInit])
const onSyncIntervalChange = (value: number) => {
setSyncInterval(value)
dispatch(setS3Partial({ syncInterval: value, autoSync: value !== 0 }))
@ -192,37 +197,6 @@ const S3Settings: FC = () => {
</SettingTitle>
<SettingHelpText>{t('settings.data.s3.title.help')}</SettingHelpText>
<SettingDivider />
{/* 覆盖式单文件备份,仅在自动备份开启且保留份数=1时推荐启用 */}
<SettingRow>
<SettingRowTitle>
{t('settings.data.backup.singleFileOverwrite.title') || '覆盖式单文件备份(同名覆盖)'}
</SettingRowTitle>
<Switch
checked={singleFileOverwrite}
onChange={onSingleFileOverwriteChange}
disabled={!(syncInterval > 0 && maxBackups === 1)}
/>
</SettingRow>
<SettingRow>
<SettingHelpText>
{t('settings.data.backup.singleFileOverwrite.help') ||
'当自动备份开启且保留份数为1时使用固定文件名每次覆盖。S3 会直接覆盖同键对象。'}
</SettingHelpText>
</SettingRow>
<SettingRow>
<SettingRowTitle>{t('settings.data.backup.singleFileName.title') || '自定义文件名(可选)'}</SettingRowTitle>
<Input
placeholder={
t('settings.data.backup.singleFileName.placeholder') || '如cherry-studio.<hostname>.<device>.zip'
}
value={singleFileName}
onChange={(e) => onSingleFileNameChange(e.target.value)}
onBlur={onSingleFileNameBlur}
style={{ width: 300 }}
disabled={!singleFileOverwrite || !(syncInterval > 0 && maxBackups === 1)}
/>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.s3.endpoint.label')}</SettingRowTitle>
<Input
@ -357,6 +331,58 @@ const S3Settings: FC = () => {
<SettingRow>
<SettingHelpText>{t('settings.data.s3.skipBackupFile.help')}</SettingHelpText>
</SettingRow>
{/* 覆盖式单文件备份,仅在自动备份开启且保留份数=1时推荐启用 */}
<SettingDivider />
<SettingRow>
<SettingRowTitle>
{t('settings.data.backup.singleFileOverwrite.title') || '覆盖式单文件备份(同名覆盖)'}
</SettingRowTitle>
<Switch
checked={singleFileOverwrite}
onChange={onSingleFileOverwriteChange}
disabled={!(syncInterval > 0 && maxBackups === 1)}
/>
</SettingRow>
<SettingRow>
<SettingHelpText>
{t('settings.data.backup.singleFileOverwrite.help') || (
<div>
<p>1使</p>
<p style={{ marginTop: 8, fontSize: 12, color: 'var(--text-secondary)' }}>
S3会直接覆盖同键对象
</p>
</div>
)}
</SettingHelpText>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.backup.singleFileName.title') || '自定义文件名(可选)'}</SettingRowTitle>
<Input
placeholder={
t('settings.data.backup.singleFileName.placeholder') || '如cherry-studio.<hostname>.<device>.zip'
}
value={singleFileName}
onChange={(e) => onSingleFileNameChange(e.target.value)}
onBlur={onSingleFileNameBlur}
style={{ width: 300 }}
disabled={!singleFileOverwrite || !(syncInterval > 0 && maxBackups === 1)}
/>
</SettingRow>
<SettingRow>
<SettingHelpText>
{t('settings.data.backup.singleFileName.help') || (
<div style={{ fontSize: 12, color: 'var(--text-secondary)' }}>
<p> 使cherry-studio.[].[].zip</p>
<p>
{`{hostname}`} - {`{device}`} -
</p>
<p> {'<>:"/\\|?*'}</p>
<p> 250</p>
</div>
)}
</SettingHelpText>
</SettingRow>
{syncInterval > 0 && (
<>
<SettingDivider />

View File

@ -22,7 +22,7 @@ import {
} from '@renderer/store/settings'
import { Button, Input, Switch, Tooltip } from 'antd'
import dayjs from 'dayjs'
import { FC, useState } from 'react'
import { FC, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { SettingDivider, SettingGroup, SettingHelpText, SettingRow, SettingRowTitle, SettingTitle } from '..'
@ -63,6 +63,11 @@ const WebDavSettings: FC = () => {
const { webdavSync } = useAppSelector((state) => state.backup)
// 同步 maxBackups 状态
useEffect(() => {
setMaxBackups(webDAVMaxBackups)
}, [webDAVMaxBackups])
// 把之前备份的文件定时上传到 webdav首先先配置 webdav 的 host, port, user, pass, path
const onSyncIntervalChange = (value: number) => {
@ -193,57 +198,6 @@ const WebDavSettings: FC = () => {
<SettingGroup theme={theme}>
<SettingTitle>{t('settings.data.webdav.title')}</SettingTitle>
<SettingDivider />
{/* 覆盖式单文件备份,仅在自动备份开启且保留份数=1时推荐启用 */}
<SettingRow>
<SettingRowTitle>
{t('settings.data.backup.singleFileOverwrite.title') || '覆盖式单文件备份(同名覆盖)'}
</SettingRowTitle>
<Switch
checked={webdavSingleFileOverwrite}
onChange={onSingleFileOverwriteChange}
disabled={!(syncInterval > 0 && maxBackups === 1)}
/>
</SettingRow>
<SettingRow>
<SettingHelpText>
{t('settings.data.backup.singleFileOverwrite.help') || (
<div>
<p>1使</p>
<p style={{ marginTop: 8, fontSize: 12, color: 'var(--text-secondary)' }}>
</p>
</div>
)}
</SettingHelpText>
</SettingRow>
<SettingRow>
<SettingRowTitle>{t('settings.data.backup.singleFileName.title') || '自定义文件名(可选)'}</SettingRowTitle>
<Input
placeholder={
t('settings.data.backup.singleFileName.placeholder') || '如cherry-studio.<hostname>.<device>.zip'
}
value={webdavSingleFileName}
onChange={(e) => onSingleFileNameChange(e.target.value)}
onBlur={onSingleFileNameBlur}
style={{ width: 300 }}
disabled={!webdavSingleFileOverwrite || !(syncInterval > 0 && maxBackups === 1)}
/>
</SettingRow>
<SettingRow>
<SettingHelpText>
{t('settings.data.backup.singleFileName.help') || (
<div style={{ fontSize: 12, color: 'var(--text-secondary)' }}>
<p> 使cherry-studio.[].[].zip</p>
<p>
{`{hostname}`} - {`{device}`} -
</p>
<p> {'<>:"/\\|?*'}</p>
<p> 250</p>
</div>
)}
</SettingHelpText>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.webdav.host.label')}</SettingRowTitle>
<Input
@ -357,6 +311,58 @@ const WebDavSettings: FC = () => {
<SettingRow>
<SettingHelpText>{t('settings.data.webdav.disableStream.help')}</SettingHelpText>
</SettingRow>
{/* 覆盖式单文件备份,仅在自动备份开启且保留份数=1时推荐启用 */}
<SettingDivider />
<SettingRow>
<SettingRowTitle>
{t('settings.data.backup.singleFileOverwrite.title') || '覆盖式单文件备份(同名覆盖)'}
</SettingRowTitle>
<Switch
checked={webdavSingleFileOverwrite}
onChange={onSingleFileOverwriteChange}
disabled={!(syncInterval > 0 && maxBackups === 1)}
/>
</SettingRow>
<SettingRow>
<SettingHelpText>
{t('settings.data.backup.singleFileOverwrite.help') || (
<div>
<p>1使</p>
<p style={{ marginTop: 8, fontSize: 12, color: 'var(--text-secondary)' }}>
</p>
</div>
)}
</SettingHelpText>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.backup.singleFileName.title') || '自定义文件名(可选)'}</SettingRowTitle>
<Input
placeholder={
t('settings.data.backup.singleFileName.placeholder') || '如cherry-studio.<hostname>.<device>.zip'
}
value={webdavSingleFileName}
onChange={(e) => onSingleFileNameChange(e.target.value)}
onBlur={onSingleFileNameBlur}
style={{ width: 300 }}
disabled={!webdavSingleFileOverwrite || !(syncInterval > 0 && maxBackups === 1)}
/>
</SettingRow>
<SettingRow>
<SettingHelpText>
{t('settings.data.backup.singleFileName.help') || (
<div style={{ fontSize: 12, color: 'var(--text-secondary)' }}>
<p> 使cherry-studio.[].[].zip</p>
<p>
{`{hostname}`} - {`{device}`} -
</p>
<p> {'<>:"/\\|?*'}</p>
<p> 250</p>
</div>
)}
</SettingHelpText>
</SettingRow>
{webdavSync && syncInterval > 0 && (
<>
<SettingDivider />

View File

@ -7,6 +7,7 @@ import { NUTSTORE_HOST } from '@shared/config/nutstore'
import dayjs from 'dayjs'
import { type CreateDirectoryOptions } from 'webdav'
import { shouldSkipCleanup, validateAndSanitizeFilename } from '../utils/backupUtils'
import { getBackupData, handleData } from './BackupService'
const logger = loggerService.withContext('NutstoreService')
@ -109,10 +110,12 @@ async function cleanupOldBackups(webdavConfig: WebDavConfig, maxBackups: number)
export async function backupToNutstore({
showMessage = false,
customFileName = ''
customFileName = '',
isAutoBackup = false
}: {
showMessage?: boolean
customFileName?: string
isAutoBackup?: boolean
} = {}) {
const nutstoreToken = getNutstoreToken()
if (!nutstoreToken) {
@ -135,21 +138,37 @@ export async function backupToNutstore({
} catch (error) {
logger.error('[backupToNutstore] Failed to get device type:', error as Error)
}
const timestamp = dayjs().format('YYYYMMDDHHmmss')
const backupFileName = customFileName || `cherry-studio.${timestamp}.${deviceType}.zip`
const finalFileName = backupFileName.endsWith('.zip') ? backupFileName : `${backupFileName}.zip`
const backupData = await getBackupData()
const skipBackupFile = store.getState().nutstore.nutstoreSkipBackupFile
const maxBackups = store.getState().nutstore.nutstoreMaxBackups
const singleFileOverwrite = store.getState().nutstore.nutstoreSingleFileOverwrite
const singleFileName = store.getState().nutstore.nutstoreSingleFileName
// Handle filename generation
let finalFileName: string
if (isAutoBackup && singleFileOverwrite && maxBackups === 1) {
// Use overwrite logic for auto backup when single file overwrite is enabled
const hostname = await window.api.system.getHostname()
const name = await validateAndSanitizeFilename(singleFileName, hostname, deviceType)
finalFileName = name
} else {
// Use timestamped logic for manual backup or when overwrite is disabled
const timestamp = dayjs().format('YYYYMMDDHHmmss')
const name = customFileName || `cherry-studio.${timestamp}.${deviceType}.zip`
finalFileName = name.endsWith('.zip') ? name : `${name}.zip`
}
isManualBackupRunning = true
store.dispatch(setNutstoreSyncState({ syncing: true, lastSyncError: null }))
const backupData = await getBackupData()
const skipBackupFile = store.getState().nutstore.nutstoreSkipBackupFile
const maxBackups = store.getState().nutstore.nutstoreMaxBackups
try {
// 先清理旧备份
await cleanupOldBackups(config, maxBackups)
// Skip cleanup for single file overwrite mode when maxBackups is 1
if (!shouldSkipCleanup(maxBackups, singleFileOverwrite)) {
// 先清理旧备份
await cleanupOldBackups(config, maxBackups)
}
const isSuccess = await window.api.backup.backupToWebdav(backupData, {
...config,
@ -264,7 +283,7 @@ export async function startNutstoreAutoSync() {
isAutoBackupRunning = true
try {
logger.verbose('[Nutstore AutoSync] Starting auto backup...')
await backupToNutstore({ showMessage: false })
await backupToNutstore({ showMessage: false, isAutoBackup: true })
} catch (error) {
logger.error('[Nutstore AutoSync] Auto backup failed:', error as Error)
} finally {

View File

@ -12,6 +12,8 @@ export interface NutstoreState {
nutstoreSyncState: NutstoreSyncState
nutstoreSkipBackupFile: boolean
nutstoreMaxBackups: number
nutstoreSingleFileOverwrite: boolean
nutstoreSingleFileName: string
}
const initialState: NutstoreState = {
@ -25,7 +27,9 @@ const initialState: NutstoreState = {
lastSyncError: null
},
nutstoreSkipBackupFile: false,
nutstoreMaxBackups: 0
nutstoreMaxBackups: 0,
nutstoreSingleFileOverwrite: false,
nutstoreSingleFileName: ''
}
const nutstoreSlice = createSlice({
@ -52,6 +56,12 @@ const nutstoreSlice = createSlice({
},
setNutstoreMaxBackups: (state, action: PayloadAction<number>) => {
state.nutstoreMaxBackups = action.payload
},
setNutstoreSingleFileOverwrite: (state, action: PayloadAction<boolean>) => {
state.nutstoreSingleFileOverwrite = action.payload
},
setNutstoreSingleFileName: (state, action: PayloadAction<string>) => {
state.nutstoreSingleFileName = action.payload
}
}
})
@ -63,7 +73,9 @@ export const {
setNutstoreSyncInterval,
setNutstoreSyncState,
setNutstoreSkipBackupFile,
setNutstoreMaxBackups
setNutstoreMaxBackups,
setNutstoreSingleFileOverwrite,
setNutstoreSingleFileName
} = nutstoreSlice.actions
export default nutstoreSlice.reducer