diff --git a/src/main/services/BackupManager.ts b/src/main/services/BackupManager.ts index ea8521aa16..4d951f3698 100644 --- a/src/main/services/BackupManager.ts +++ b/src/main/services/BackupManager.ts @@ -77,7 +77,8 @@ class BackupManager { _: Electron.IpcMainInvokeEvent, fileName: string, data: string, - destinationPath: string = this.backupDir + destinationPath: string = this.backupDir, + skipBackupFile: boolean = false ): Promise { const mainWindow = windowService.getMainWindow() @@ -104,23 +105,30 @@ class BackupManager { onProgress({ stage: 'writing_data', progress: 20, total: 100 }) - // 复制 Data 目录到临时目录 - const sourcePath = path.join(app.getPath('userData'), 'Data') - const tempDataDir = path.join(this.tempDir, 'Data') + Logger.log('[BackupManager IPC] ', skipBackupFile) - // 获取源目录总大小 - const totalSize = await this.getDirSize(sourcePath) - let copiedSize = 0 + if (!skipBackupFile) { + // 复制 Data 目录到临时目录 + const sourcePath = path.join(app.getPath('userData'), 'Data') + const tempDataDir = path.join(this.tempDir, 'Data') - // 使用流式复制 - await this.copyDirWithProgress(sourcePath, tempDataDir, (size) => { - copiedSize += size - const progress = Math.min(50, Math.floor((copiedSize / totalSize) * 50)) - onProgress({ stage: 'copying_files', progress, total: 100 }) - }) + // 获取源目录总大小 + const totalSize = await this.getDirSize(sourcePath) + let copiedSize = 0 - await this.setWritableRecursive(tempDataDir) - onProgress({ stage: 'preparing_compression', progress: 50, total: 100 }) + // 使用流式复制 + await this.copyDirWithProgress(sourcePath, tempDataDir, (size) => { + copiedSize += size + const progress = Math.min(50, Math.floor((copiedSize / totalSize) * 50)) + onProgress({ stage: 'copying_files', progress, total: 100 }) + }) + + await this.setWritableRecursive(tempDataDir) + onProgress({ stage: 'preparing_compression', progress: 50, total: 100 }) + } else { + Logger.log('[BackupManager] Skip the backup of the file') + await fs.promises.mkdir(path.join(this.tempDir, 'Data')) // 不创建空 Data 目录会导致 restore 失败 + } // 创建输出文件流 const backupedFilePath = path.join(destinationPath, fileName) @@ -279,7 +287,7 @@ class BackupManager { async backupToWebdav(_: Electron.IpcMainInvokeEvent, data: string, webdavConfig: WebDavConfig) { const filename = webdavConfig.fileName || 'cherry-studio.backup.zip' - const backupedFilePath = await this.backup(_, filename, data) + const backupedFilePath = await this.backup(_, filename, data, undefined, webdavConfig.skipBackupFile) const webdavClient = new WebDav(webdavConfig) try { const result = await webdavClient.putFileContents(filename, fs.createReadStream(backupedFilePath), { diff --git a/src/preload/index.ts b/src/preload/index.ts index 828557865d..3679dd0802 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -37,8 +37,8 @@ const api = { decompress: (text: Buffer) => ipcRenderer.invoke(IpcChannel.Zip_Decompress, text) }, backup: { - backup: (fileName: string, data: string, destinationPath?: string) => - ipcRenderer.invoke(IpcChannel.Backup_Backup, fileName, data, destinationPath), + backup: (fileName: string, data: string, destinationPath?: string, skipBackupFile?: boolean) => + ipcRenderer.invoke(IpcChannel.Backup_Backup, fileName, data, destinationPath, skipBackupFile), restore: (backupPath: string) => ipcRenderer.invoke(IpcChannel.Backup_Restore, backupPath), backupToWebdav: (data: string, webdavConfig: WebDavConfig) => ipcRenderer.invoke(IpcChannel.Backup_BackupToWebdav, data, webdavConfig), diff --git a/src/renderer/src/components/Popups/BackupPopup.tsx b/src/renderer/src/components/Popups/BackupPopup.tsx index 41eb268a16..dd19e0010b 100644 --- a/src/renderer/src/components/Popups/BackupPopup.tsx +++ b/src/renderer/src/components/Popups/BackupPopup.tsx @@ -1,6 +1,8 @@ import { backup } from '@renderer/services/BackupService' +import store from '@renderer/store' import { IpcChannel } from '@shared/IpcChannel' import { Modal, Progress } from 'antd' +import Logger from 'electron-log' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -20,6 +22,7 @@ const PopupContainer: React.FC = ({ resolve }) => { const [open, setOpen] = useState(true) const [progressData, setProgressData] = useState() const { t } = useTranslation() + const skipBackupFile = store.getState().settings.skipBackupFile useEffect(() => { const removeListener = window.electron.ipcRenderer.on(IpcChannel.BackupProgress, (_, data: ProgressData) => { @@ -32,7 +35,8 @@ const PopupContainer: React.FC = ({ resolve }) => { }, []) const onOk = async () => { - await backup() + Logger.log('[BackupManager] ', skipBackupFile) + await backup(skipBackupFile) setOpen(false) } diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 2866739748..5c1e47f053 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -956,6 +956,8 @@ "app_knowledge.remove_all_confirm": "Deleting knowledge base files will reduce the storage space occupied, but will not delete the knowledge base vector data, after deletion, the source file will no longer be able to be opened. Continue?", "app_knowledge.remove_all_success": "Files removed successfully", "app_logs": "App Logs", + "backup.skip_file_data_title": "Slim Backup", + "backup.skip_file_data_help": "Skip backing up data files such as pictures and knowledge bases during backup, and only back up chat records and settings. Reduce space occupancy and speed up the backup speed.", "clear_cache": { "button": "Clear Cache", "confirm": "Clearing the cache will delete application cache data, including minapp data. This action is irreversible, continue?", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index 9be762b226..ca3f98c4a3 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -953,6 +953,8 @@ "app_knowledge.remove_all": "ナレッジベースファイルを削除", "app_knowledge.remove_all_confirm": "ナレッジベースファイルを削除すると、ナレッジベース自体は削除されません。これにより、ストレージ容量を節約できます。続行しますか?", "app_knowledge.remove_all_success": "ファイル削除成功", + "backup.skip_file_data_title": "精簡バックアップ", + "backup.skip_file_data_help": "バックアップ時に、画像や知識ベースなどのデータファイルをバックアップ対象から除外し、チャット履歴と設定のみをバックアップします。スペースの占有を減らし、バックアップ速度を向上させます。", "app_logs": "アプリログ", "clear_cache": { "button": "キャッシュをクリア", diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index f749fb2d01..003ac5a446 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -904,6 +904,7 @@ "restore": { "confirm": "Вы уверены, что хотите восстановить данные?", "confirm.button": "Выбрать файл резервной копии", + "content": "Операция восстановления перезапишет все текущие данные приложения данными из резервной копии. Это может занять некоторое время.", "progress": { "completed": "Восстановление завершено", @@ -954,6 +955,8 @@ "app_knowledge.remove_all_confirm": "Удаление файлов базы знаний не удалит саму базу знаний, что позволит уменьшить занимаемый объем памяти, продолжить?", "app_knowledge.remove_all_success": "Файлы удалены успешно", "app_logs": "Логи приложения", + "backup.skip_file_data_title": "Упрощенная резервная копия", + "backup.skip_file_data_help": "Пропустить при резервном копировании такие данные, как изображения, базы знаний и другие файлы данных, и сделать резервную копию только переписки и настроек. Это уменьшает использование места на диске и ускоряет процесс резервного копирования.", "clear_cache": { "button": "Очистка кэша", "confirm": "Очистка кэша удалит данные приложения. Это действие необратимо, продолжить?", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index e787498e8b..9bbdb5aa4e 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -956,6 +956,8 @@ "app_knowledge.remove_all_confirm": "删除知识库文件可以减少存储空间占用,但不会删除知识库向量化数据,删除之后将无法打开源文件,是否删除?", "app_knowledge.remove_all_success": "文件删除成功", "app_logs": "应用日志", + "backup.skip_file_data_title": "精简备份", + "backup.skip_file_data_help": "备份时跳过备份图片、知识库等数据文件,仅备份聊天记录和设置。减少空间占用, 加快备份速度。", "clear_cache": { "button": "清除缓存", "confirm": "清除缓存将删除应用缓存的数据,包括小程序数据。此操作不可恢复,是否继续?", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 8b1eff5941..085b50323a 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -956,6 +956,8 @@ "app_knowledge.remove_all_confirm": "刪除知識庫文件可以減少儲存空間佔用,但不會刪除知識庫向量化資料,刪除之後將無法開啟原始檔,是否刪除?", "app_knowledge.remove_all_success": "檔案刪除成功", "app_logs": "應用程式日誌", + "backup.skip_file_data_title": "精簡備份", + "backup.skip_file_data_help": "備份時跳過備份圖片、知識庫等數據文件,僅備份聊天記錄和設置。減少空間佔用, 加快備份速度。", "clear_cache": { "button": "清除快取", "confirm": "清除快取將刪除應用快取資料,包括小工具資料。此操作不可恢復,是否繼續?", diff --git a/src/renderer/src/i18n/translate/el-gr.json b/src/renderer/src/i18n/translate/el-gr.json index 8f2cb2b295..394d271858 100644 --- a/src/renderer/src/i18n/translate/el-gr.json +++ b/src/renderer/src/i18n/translate/el-gr.json @@ -930,6 +930,8 @@ "app_knowledge.remove_all_confirm": "Η διαγραφή των αρχείων της βάσης γνώσεων μπορεί να μειώσει τη χρήση χώρου αποθήκευσης, αλλά δεν θα διαγράψει τα διανυσματωτικά δεδομένα της βάσης γνώσεων. Μετά τη διαγραφή, δεν θα μπορείτε να ανοίξετε τα αρχεία πηγή. Θέλετε να διαγράψετε;", "app_knowledge.remove_all_success": "Τα αρχεία διαγράφηκαν με επιτυχία", "app_logs": "Φάκελοι εφαρμογής", + "backup.skip_file_data_title": "Συμπυκνωμένο αντίγραφο ασφαλείας", + "backup.skip_file_data_help": "Κατά τη δημιουργία αντιγράφων ασφαλείας, παραλείψτε τις εικόνες, τις βάσεις γνώσεων και άλλα αρχεία δεδομένων. Δημιουργήστε αντίγραφα μόνο για το ιστορικό συνομιλιών και τις ρυθμίσεις. Αυτό θα μειώσει τη χρήση χώρου και θα επιταχύνει την ταχύτητα δημιουργίας αντιγράφων.", "clear_cache": { "button": "Καθαρισμός Μνήμης", "confirm": "Η διαγραφή της μνήμης θα διαγράψει τα στοιχεία καθαρισμού της εφαρμογής, συμπεριλαμβανομένων των στοιχείων πρόσθετων εφαρμογών. Αυτή η ενέργεια δεν είναι αναστρέψιμη. Θέλετε να συνεχίσετε;", @@ -1655,4 +1657,4 @@ "visualization": "προβολή" } } -} \ No newline at end of file +} diff --git a/src/renderer/src/i18n/translate/es-es.json b/src/renderer/src/i18n/translate/es-es.json index 8dc1ad294a..f71f859ce2 100644 --- a/src/renderer/src/i18n/translate/es-es.json +++ b/src/renderer/src/i18n/translate/es-es.json @@ -107,6 +107,7 @@ "backup": { "confirm": "¿Está seguro de que desea realizar una copia de seguridad de los datos?", "confirm.button": "Seleccionar ubicación de copia de seguridad", + "confirm.file_checkbox": "El tamaño del archivo es {{size}}, ¿desea elegir el archivo de copia de seguridad?", "content": "Realizar una copia de seguridad de todos los datos, incluyendo registros de chat, configuraciones, bases de conocimiento y todos los demás datos. Tenga en cuenta que el proceso de copia de seguridad puede llevar algún tiempo, gracias por su paciencia.", "progress": { "completed": "Copia de seguridad completada", @@ -1655,4 +1656,4 @@ "visualization": "Visualización" } } -} \ No newline at end of file +} diff --git a/src/renderer/src/i18n/translate/fr-fr.json b/src/renderer/src/i18n/translate/fr-fr.json index d80f63c02b..725f89717c 100644 --- a/src/renderer/src/i18n/translate/fr-fr.json +++ b/src/renderer/src/i18n/translate/fr-fr.json @@ -930,6 +930,8 @@ "app_knowledge.remove_all_confirm": "La suppression des fichiers de la base de connaissances libérera de l'espace de stockage, mais ne supprimera pas les données vectorisées de la base de connaissances. Après la suppression, vous ne pourrez plus ouvrir les fichiers sources. Souhaitez-vous continuer ?", "app_knowledge.remove_all_success": "Fichiers supprimés avec succès", "app_logs": "Journaux de l'application", + "backup.skip_file_data_title": "Sauvegarde réduite", + "backup.skip_file_data_help": "Passer outre les fichiers de données tels que les images et les bases de connaissances lors de la sauvegarde, et ne sauvegarder que les conversations et les paramètres. Cela réduit l'occupation d'espace et accélère la vitesse de sauvegarde.", "clear_cache": { "button": "Effacer le cache", "confirm": "L'effacement du cache supprimera les données du cache de l'application, y compris les données des mini-programmes. Cette action ne peut pas être annulée, voulez-vous continuer ?", @@ -1655,4 +1657,4 @@ "visualization": "Visualisation" } } -} \ No newline at end of file +} diff --git a/src/renderer/src/i18n/translate/pt-pt.json b/src/renderer/src/i18n/translate/pt-pt.json index 52ae447027..d4b5362404 100644 --- a/src/renderer/src/i18n/translate/pt-pt.json +++ b/src/renderer/src/i18n/translate/pt-pt.json @@ -107,6 +107,7 @@ "backup": { "confirm": "Tem certeza de que deseja fazer backup dos dados?", "confirm.button": "Escolher local de backup", + "confirm.file_checkbox": "Pule a cópia de segurança de arquivos de dados como imagens e banco de conhecimento e copie apenas as conversas e as configurações.", "content": "Fazer backup de todos os dados, incluindo registros de chat, configurações, base de conhecimento e todos os outros dados. Por favor, note que o processo de backup pode levar algum tempo. Agradecemos sua paciência.", "progress": { "completed": "Backup concluído", @@ -931,6 +932,8 @@ "app_knowledge.remove_all_confirm": "A exclusão dos arquivos da base de conhecimento reduzirá o uso do espaço de armazenamento, mas não excluirá os dados vetoriais da base de conhecimento. Após a exclusão, os arquivos originais não poderão ser abertos. Deseja excluir?", "app_knowledge.remove_all_success": "Arquivo excluído com sucesso", "app_logs": "Logs do aplicativo", + "backup.skip_file_data_title": "Backup simplificado", + "backup.skip_file_data_help": "Pule arquivos de dados como imagens e bancos de conhecimento durante o backup e realize apenas o backup das conversas e configurações. Diminua o consumo de espaço e aumente a velocidade do backup.", "clear_cache": { "button": "Limpar cache", "confirm": "Limpar cache removerá os dados armazenados em cache do aplicativo, incluindo dados de aplicativos minúsculos. Esta ação não pode ser desfeita, deseja continuar?", @@ -1656,4 +1659,4 @@ "visualization": "Visualização" } } -} \ No newline at end of file +} diff --git a/src/renderer/src/pages/settings/DataSettings/DataSettings.tsx b/src/renderer/src/pages/settings/DataSettings/DataSettings.tsx index ec31fd6ae7..90e25de51a 100644 --- a/src/renderer/src/pages/settings/DataSettings/DataSettings.tsx +++ b/src/renderer/src/pages/settings/DataSettings/DataSettings.tsx @@ -14,15 +14,25 @@ import RestorePopup from '@renderer/components/Popups/RestorePopup' import { useTheme } from '@renderer/context/ThemeProvider' import { useKnowledgeFiles } from '@renderer/hooks/useKnowledgeFiles' import { reset } from '@renderer/services/BackupService' +import store, { useAppDispatch } from '@renderer/store' +import { setSkipBackupFile as _setSkipBackupFile } from '@renderer/store/settings' import { AppInfo } from '@renderer/types' import { formatFileSize } from '@renderer/utils' -import { Button, Typography } from 'antd' +import { Button, Switch, Typography } from 'antd' import { FileText, FolderCog, FolderInput, Sparkle } from 'lucide-react' import { FC, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' -import { SettingContainer, SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..' +import { + SettingContainer, + SettingDivider, + SettingGroup, + SettingHelpText, + SettingRow, + SettingRowTitle, + SettingTitle +} from '..' import AgentsSubscribeUrlSettings from './AgentsSubscribeUrlSettings' import ExportMenuOptions from './ExportMenuSettings' import JoplinSettings from './JoplinSettings' @@ -42,6 +52,11 @@ const DataSettings: FC = () => { const { theme } = useTheme() const [menu, setMenu] = useState('data') + const _skipBackupFile = store.getState().settings.skipBackupFile + const [skipBackupFile, setSkipBackupFile] = useState(_skipBackupFile) + + const dispatch = useAppDispatch() + //joplin icon needs to be updated into iconfont const JoplinIcon = () => ( @@ -164,6 +179,11 @@ const DataSettings: FC = () => { }) } + const onSkipBackupFilesChange = (value: boolean) => { + setSkipBackupFile(value) + dispatch(_setSkipBackupFile(value)) + } + return ( @@ -208,6 +228,14 @@ const DataSettings: FC = () => { + + + {t('settings.data.backup.skip_file_data_title')} + + + + {t('settings.data.backup.skip_file_data_help')} + {t('settings.data.data.title')} diff --git a/src/renderer/src/pages/settings/DataSettings/NutstoreSettings.tsx b/src/renderer/src/pages/settings/DataSettings/NutstoreSettings.tsx index 8ef3432307..6b9784cea0 100644 --- a/src/renderer/src/pages/settings/DataSettings/NutstoreSettings.tsx +++ b/src/renderer/src/pages/settings/DataSettings/NutstoreSettings.tsx @@ -17,25 +17,31 @@ import { useAppDispatch, useAppSelector } from '@renderer/store' import { setNutstoreAutoSync, setNutstorePath, + setNutstoreSkipBackupFile, setNutstoreSyncInterval, setNutstoreToken } from '@renderer/store/nutstore' import { modalConfirm } from '@renderer/utils' import { NUTSTORE_HOST } from '@shared/config/nutstore' -import { Button, Input, Select, Tooltip, Typography } from 'antd' +import { Button, Input, Select, Switch, Tooltip, Typography } from 'antd' import dayjs from 'dayjs' import { FC, useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { type FileStat } from 'webdav' -import { SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..' +import { SettingDivider, SettingGroup, SettingHelpText, SettingRow, SettingRowTitle, SettingTitle } from '..' const NutstoreSettings: FC = () => { const { theme } = useTheme() const { t } = useTranslation() - const { nutstoreToken, nutstorePath, nutstoreSyncInterval, nutstoreAutoSync, nutstoreSyncState } = useAppSelector( - (state) => state.nutstore - ) + const { + nutstoreToken, + nutstorePath, + nutstoreSyncInterval, + nutstoreAutoSync, + nutstoreSyncState, + nutstoreSkipBackupFile + } = useAppSelector((state) => state.nutstore) const dispatch = useAppDispatch() @@ -48,6 +54,8 @@ const NutstoreSettings: FC = () => { const [syncInterval, setSyncInterval] = useState(nutstoreSyncInterval) + const [nutSkipBackupFile, setNutSkipBackupFile] = useState(nutstoreSkipBackupFile) + const nutstoreSSOHandler = useNutstoreSSO() const [backupManagerVisible, setBackupManagerVisible] = useState(false) @@ -128,6 +136,11 @@ const NutstoreSettings: FC = () => { } } + const onSkipBackupFilesChange = (value: boolean) => { + setNutSkipBackupFile(value) + dispatch(setNutstoreSkipBackupFile(value)) + } + const handleClickPathChange = async () => { if (!nutstoreToken) { return @@ -287,6 +300,14 @@ const NutstoreSettings: FC = () => { )} + + + {t('settings.data.backup.skip_file_data_title')} + + + + {t('settings.data.backup.skip_file_data_help')} + )} <> diff --git a/src/renderer/src/pages/settings/DataSettings/WebDavSettings.tsx b/src/renderer/src/pages/settings/DataSettings/WebDavSettings.tsx index 29221f9c05..104fa5cbd8 100644 --- a/src/renderer/src/pages/settings/DataSettings/WebDavSettings.tsx +++ b/src/renderer/src/pages/settings/DataSettings/WebDavSettings.tsx @@ -12,15 +12,16 @@ import { setWebdavMaxBackups as _setWebdavMaxBackups, setWebdavPass as _setWebdavPass, setWebdavPath as _setWebdavPath, + setWebdavSkipBackupFile as _setWebdavSkipBackupFile, setWebdavSyncInterval as _setWebdavSyncInterval, setWebdavUser as _setWebdavUser } from '@renderer/store/settings' -import { Button, Input, Select, Tooltip } from 'antd' +import { Button, Input, Select, Switch, Tooltip } from 'antd' import dayjs from 'dayjs' import { FC, useState } from 'react' import { useTranslation } from 'react-i18next' -import { SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..' +import { SettingDivider, SettingGroup, SettingHelpText, SettingRow, SettingRowTitle, SettingTitle } from '..' const WebDavSettings: FC = () => { const { @@ -29,13 +30,15 @@ const WebDavSettings: FC = () => { webdavPass: webDAVPass, webdavPath: webDAVPath, webdavSyncInterval: webDAVSyncInterval, - webdavMaxBackups: webDAVMaxBackups + webdavMaxBackups: webDAVMaxBackups, + webdavSkipBackupFile: webdDAVSkipBackupFile } = useSettings() const [webdavHost, setWebdavHost] = useState(webDAVHost) const [webdavUser, setWebdavUser] = useState(webDAVUser) const [webdavPass, setWebdavPass] = useState(webDAVPass) const [webdavPath, setWebdavPath] = useState(webDAVPath) + const [webdavSkipBackupFile, setWebdavSkipBackupFile] = useState(webdDAVSkipBackupFile) const [backupManagerVisible, setBackupManagerVisible] = useState(false) const [syncInterval, setSyncInterval] = useState(webDAVSyncInterval) @@ -67,6 +70,11 @@ const WebDavSettings: FC = () => { dispatch(_setWebdavMaxBackups(value)) } + const onSkipBackupFilesChange = (value: boolean) => { + setWebdavSkipBackupFile(value) + dispatch(_setWebdavSkipBackupFile(value)) + } + const renderSyncStatus = () => { if (!webdavHost) return null @@ -194,6 +202,14 @@ const WebDavSettings: FC = () => { 50 + + + {t('settings.data.backup.skip_file_data_title')} + + + + {t('settings.data.backup.skip_file_data_help')} + {webdavSync && syncInterval > 0 && ( <> diff --git a/src/renderer/src/services/BackupService.ts b/src/renderer/src/services/BackupService.ts index a40b94e0fb..e0720e36d3 100644 --- a/src/renderer/src/services/BackupService.ts +++ b/src/renderer/src/services/BackupService.ts @@ -6,12 +6,12 @@ import store from '@renderer/store' import { setWebDAVSyncState } from '@renderer/store/backup' import dayjs from 'dayjs' -export async function backup() { +export async function backup(skipBackupFile: boolean) { const filename = `cherry-studio.${dayjs().format('YYYYMMDDHHmm')}.zip` const fileContnet = await getBackupData() const selectFolder = await window.api.file.selectFolder() if (selectFolder) { - await window.api.backup.backup(filename, fileContnet, selectFolder) + await window.api.backup.backup(filename, fileContnet, selectFolder, skipBackupFile) window.message.success({ content: i18n.t('message.backup.success'), key: 'backup' }) } } @@ -83,7 +83,8 @@ export async function backupToWebdav({ store.dispatch(setWebDAVSyncState({ syncing: true, lastSyncError: null })) - const { webdavHost, webdavUser, webdavPass, webdavPath, webdavMaxBackups } = store.getState().settings + const { webdavHost, webdavUser, webdavPass, webdavPath, webdavMaxBackups, webdavSkipBackupFile } = + store.getState().settings let deviceType = 'unknown' let hostname = 'unknown' try { @@ -104,7 +105,8 @@ export async function backupToWebdav({ webdavUser, webdavPass, webdavPath, - fileName: finalFileName + fileName: finalFileName, + skipBackupFile: webdavSkipBackupFile }) if (success) { store.dispatch( diff --git a/src/renderer/src/services/NutstoreService.ts b/src/renderer/src/services/NutstoreService.ts index bec3f477e7..e576a37ff3 100644 --- a/src/renderer/src/services/NutstoreService.ts +++ b/src/renderer/src/services/NutstoreService.ts @@ -98,9 +98,13 @@ export async function backupToNutstore({ store.dispatch(setNutstoreSyncState({ syncing: true, lastSyncError: null })) const backupData = await getBackupData() - + const skipBackupFile = store.getState().nutstore.nutstoreSkipBackupFile try { - const isSuccess = await window.api.backup.backupToWebdav(backupData, { ...config, fileName: finalFileName }) + const isSuccess = await window.api.backup.backupToWebdav(backupData, { + ...config, + fileName: finalFileName, + skipBackupFile: skipBackupFile + }) if (isSuccess) { store.dispatch( diff --git a/src/renderer/src/store/nutstore.ts b/src/renderer/src/store/nutstore.ts index 97542a30be..354a93bd39 100644 --- a/src/renderer/src/store/nutstore.ts +++ b/src/renderer/src/store/nutstore.ts @@ -10,6 +10,7 @@ export interface NutstoreState { nutstoreAutoSync: boolean nutstoreSyncInterval: number nutstoreSyncState: NutstoreSyncState + nutstoreSkipBackupFile: boolean } const initialState: NutstoreState = { @@ -21,7 +22,8 @@ const initialState: NutstoreState = { lastSyncTime: null, syncing: false, lastSyncError: null - } + }, + nutstoreSkipBackupFile: false } const nutstoreSlice = createSlice({ @@ -42,11 +44,20 @@ const nutstoreSlice = createSlice({ }, setNutstoreSyncState: (state, action: PayloadAction>) => { state.nutstoreSyncState = { ...state.nutstoreSyncState, ...action.payload } + }, + setNutstoreSkipBackupFile: (state, action: PayloadAction) => { + state.nutstoreSkipBackupFile = action.payload } } }) -export const { setNutstoreToken, setNutstorePath, setNutstoreAutoSync, setNutstoreSyncInterval, setNutstoreSyncState } = - nutstoreSlice.actions +export const { + setNutstoreToken, + setNutstorePath, + setNutstoreAutoSync, + setNutstoreSyncInterval, + setNutstoreSyncState, + setNutstoreSkipBackupFile +} = nutstoreSlice.actions export default nutstoreSlice.reducer diff --git a/src/renderer/src/store/settings.ts b/src/renderer/src/store/settings.ts index 81a06c756b..f68eac0a4a 100644 --- a/src/renderer/src/store/settings.ts +++ b/src/renderer/src/store/settings.ts @@ -77,6 +77,8 @@ export interface SettingsState { gridColumns: number gridPopoverTrigger: 'hover' | 'click' messageNavigation: 'none' | 'buttons' | 'anchor' + // 数据目录设置 + skipBackupFile: boolean // webdav 配置 host, user, pass, path webdavHost: string webdavUser: string @@ -85,6 +87,7 @@ export interface SettingsState { webdavAutoSync: boolean webdavSyncInterval: number webdavMaxBackups: number + webdavSkipBackupFile: boolean translateModelPrompt: string autoTranslateWithSpace: boolean showTranslateConfirm: boolean @@ -202,6 +205,7 @@ export const initialState: SettingsState = { gridColumns: 2, gridPopoverTrigger: 'click', messageNavigation: 'none', + skipBackupFile: false, webdavHost: '', webdavUser: '', webdavPass: '', @@ -209,6 +213,7 @@ export const initialState: SettingsState = { webdavAutoSync: false, webdavSyncInterval: 0, webdavMaxBackups: 0, + webdavSkipBackupFile: false, translateModelPrompt: TRANSLATE_PROMPT, autoTranslateWithSpace: false, showTranslateConfirm: true, @@ -356,6 +361,9 @@ const settingsSlice = createSlice({ setClickAssistantToShowTopic: (state, action: PayloadAction) => { state.clickAssistantToShowTopic = action.payload }, + setSkipBackupFile: (state, action: PayloadAction) => { + state.skipBackupFile = action.payload + }, setWebdavHost: (state, action: PayloadAction) => { state.webdavHost = action.payload }, @@ -377,6 +385,9 @@ const settingsSlice = createSlice({ setWebdavMaxBackups: (state, action: PayloadAction) => { state.webdavMaxBackups = action.payload }, + setWebdavSkipBackupFile: (state, action: PayloadAction) => { + state.webdavSkipBackupFile = action.payload + }, setCodeExecution: (state, action: PayloadAction<{ enabled?: boolean; timeoutMinutes?: number }>) => { if (action.payload.enabled !== undefined) { state.codeExecution.enabled = action.payload.enabled @@ -611,6 +622,7 @@ export const { setAutoCheckUpdate, setRenderInputMessageAsMarkdown, setClickAssistantToShowTopic, + setSkipBackupFile, setWebdavHost, setWebdavUser, setWebdavPass, @@ -618,6 +630,7 @@ export const { setWebdavAutoSync, setWebdavSyncInterval, setWebdavMaxBackups, + setWebdavSkipBackupFile, setCodeExecution, setCodeEditor, setCodePreview, diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index 2a0d77260e..dabfd60491 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -313,6 +313,7 @@ export type WebDavConfig = { webdavPass: string webdavPath: string fileName?: string + skipBackupFile?: boolean } export type AppInfo = {