diff --git a/packages/shared/IpcChannel.ts b/packages/shared/IpcChannel.ts index ca49bd40c5..daea5dad6e 100644 --- a/packages/shared/IpcChannel.ts +++ b/packages/shared/IpcChannel.ts @@ -153,11 +153,6 @@ export enum IpcChannel { Backup_CheckConnection = 'backup:checkConnection', Backup_CreateDirectory = 'backup:createDirectory', Backup_DeleteWebdavFile = 'backup:deleteWebdavFile', - Backup_BackupToS3 = 'backup:backupToS3', - Backup_RestoreFromS3 = 'backup:restoreFromS3', - Backup_ListS3Files = 'backup:listS3Files', - Backup_DeleteS3File = 'backup:deleteS3File', - Backup_CheckS3Connection = 'backup:checkS3Connection', // zip Zip_Compress = 'zip:compress', diff --git a/src/main/ipc.ts b/src/main/ipc.ts index af043c7c8c..8c6810bcdc 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -344,11 +344,6 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { ipcMain.handle(IpcChannel.Backup_CheckConnection, backupManager.checkConnection) ipcMain.handle(IpcChannel.Backup_CreateDirectory, backupManager.createDirectory) ipcMain.handle(IpcChannel.Backup_DeleteWebdavFile, backupManager.deleteWebdavFile) - ipcMain.handle(IpcChannel.Backup_BackupToS3, backupManager.backupToS3) - ipcMain.handle(IpcChannel.Backup_RestoreFromS3, backupManager.restoreFromS3) - ipcMain.handle(IpcChannel.Backup_ListS3Files, backupManager.listS3Files) - ipcMain.handle(IpcChannel.Backup_DeleteS3File, backupManager.deleteS3File) - ipcMain.handle(IpcChannel.Backup_CheckS3Connection, backupManager.checkS3Connection) // file ipcMain.handle(IpcChannel.File_Open, fileManager.open) diff --git a/src/main/services/BackupManager.ts b/src/main/services/BackupManager.ts index 6e0c813e6d..e994e90bed 100644 --- a/src/main/services/BackupManager.ts +++ b/src/main/services/BackupManager.ts @@ -1,6 +1,5 @@ import { IpcChannel } from '@shared/IpcChannel' import { WebDavConfig } from '@types' -import { S3Config } from '@types' import archiver from 'archiver' import { exec } from 'child_process' import { app } from 'electron' @@ -11,7 +10,6 @@ import * as path from 'path' import { CreateDirectoryOptions, FileStat } from 'webdav' import { getDataPath } from '../utils' -import S3Storage from './RemoteStorage' import WebDav from './WebDav' import { windowService } from './WindowService' @@ -27,11 +25,6 @@ class BackupManager { this.restoreFromWebdav = this.restoreFromWebdav.bind(this) this.listWebdavFiles = this.listWebdavFiles.bind(this) this.deleteWebdavFile = this.deleteWebdavFile.bind(this) - this.backupToS3 = this.backupToS3.bind(this) - this.restoreFromS3 = this.restoreFromS3.bind(this) - this.listS3Files = this.listS3Files.bind(this) - this.deleteS3File = this.deleteS3File.bind(this) - this.checkS3Connection = this.checkS3Connection.bind(this) } private async setWritableRecursive(dirPath: string): Promise { @@ -92,11 +85,7 @@ class BackupManager { const onProgress = (processData: { stage: string; progress: number; total: number }) => { mainWindow?.webContents.send(IpcChannel.BackupProgress, processData) - // 只在关键阶段记录日志:开始、结束和主要阶段转换点 - const logStages = ['preparing', 'writing_data', 'preparing_compression', 'completed'] - if (logStages.includes(processData.stage) || processData.progress === 100) { - Logger.log('[BackupManager] backup progress', processData) - } + Logger.log('[BackupManager] backup progress', processData) } try { @@ -158,23 +147,18 @@ class BackupManager { let totalBytes = 0 let processedBytes = 0 - // 首先计算总文件数和总大小,但不记录详细日志 + // 首先计算总文件数和总大小 const calculateTotals = async (dirPath: string) => { - try { - const items = await fs.readdir(dirPath, { withFileTypes: true }) - for (const item of items) { - const fullPath = path.join(dirPath, item.name) - if (item.isDirectory()) { - await calculateTotals(fullPath) - } else { - totalEntries++ - const stats = await fs.stat(fullPath) - totalBytes += stats.size - } + const items = await fs.readdir(dirPath, { withFileTypes: true }) + for (const item of items) { + const fullPath = path.join(dirPath, item.name) + if (item.isDirectory()) { + await calculateTotals(fullPath) + } else { + totalEntries++ + const stats = await fs.stat(fullPath) + totalBytes += stats.size } - } catch (error) { - // 仅在出错时记录日志 - Logger.error('[BackupManager] Error calculating totals:', error) } } @@ -246,11 +230,7 @@ class BackupManager { const onProgress = (processData: { stage: string; progress: number; total: number }) => { mainWindow?.webContents.send(IpcChannel.RestoreProgress, processData) - // 只在关键阶段记录日志 - const logStages = ['preparing', 'extracting', 'extracted', 'reading_data', 'completed'] - if (logStages.includes(processData.stage) || processData.progress === 100) { - Logger.log('[BackupManager] restore progress', processData) - } + Logger.log('[BackupManager] restore progress', processData) } try { @@ -402,54 +382,21 @@ class BackupManager { destination: string, onProgress: (size: number) => void ): Promise { - // 先统计总文件数 - let totalFiles = 0 - let processedFiles = 0 - let lastProgressReported = 0 + const items = await fs.readdir(source, { withFileTypes: true }) - // 计算总文件数 - const countFiles = async (dir: string): Promise => { - let count = 0 - const items = await fs.readdir(dir, { withFileTypes: true }) - for (const item of items) { - if (item.isDirectory()) { - count += await countFiles(path.join(dir, item.name)) - } else { - count++ - } - } - return count - } + for (const item of items) { + const sourcePath = path.join(source, item.name) + const destPath = path.join(destination, item.name) - totalFiles = await countFiles(source) - - // 复制文件并更新进度 - const copyDir = async (src: string, dest: string): Promise => { - const items = await fs.readdir(src, { withFileTypes: true }) - - for (const item of items) { - const sourcePath = path.join(src, item.name) - const destPath = path.join(dest, item.name) - - if (item.isDirectory()) { - await fs.ensureDir(destPath) - await copyDir(sourcePath, destPath) - } else { - const stats = await fs.stat(sourcePath) - await fs.copy(sourcePath, destPath) - processedFiles++ - - // 只在进度变化超过5%时报告进度 - const currentProgress = Math.floor((processedFiles / totalFiles) * 100) - if (currentProgress - lastProgressReported >= 5 || processedFiles === totalFiles) { - lastProgressReported = currentProgress - onProgress(stats.size) - } - } + if (item.isDirectory()) { + await fs.ensureDir(destPath) + await this.copyDirWithProgress(sourcePath, destPath, onProgress) + } else { + const stats = await fs.stat(sourcePath) + await fs.copy(sourcePath, destPath) + onProgress(stats.size) } } - - await copyDir(source, destination) } async checkConnection(_: Electron.IpcMainInvokeEvent, webdavConfig: WebDavConfig) { @@ -476,141 +423,6 @@ class BackupManager { throw new Error(error.message || 'Failed to delete backup file') } } - - async backupToS3(_: Electron.IpcMainInvokeEvent, data: string, s3Config: S3Config) { - // 获取设备名 - const os = require('os') - const deviceName = os.hostname ? os.hostname() : 'device' - const timestamp = new Date() - .toISOString() - .replace(/[-:T.Z]/g, '') - .slice(0, 14) - const filename = s3Config.fileName || `cherry-studio.backup.${deviceName}.${timestamp}.zip` - - // 不记录详细日志,只记录开始和结束 - Logger.log(`[BackupManager] Starting S3 backup to ${filename}`) - - const backupedFilePath = await this.backup(_, filename, data, undefined, s3Config.skipBackupFile) - const s3Client = new S3Storage('s3', { - endpoint: s3Config.endpoint, - region: s3Config.region, - bucket: s3Config.bucket, - access_key_id: s3Config.access_key_id, - secret_access_key: s3Config.secret_access_key, - root: s3Config.root || '' - }) - try { - const fileBuffer = await fs.promises.readFile(backupedFilePath) - const result = await s3Client.putFileContents(filename, fileBuffer) - await fs.remove(backupedFilePath) - - Logger.log(`[BackupManager] S3 backup completed successfully: ${filename}`) - return result - } catch (error) { - Logger.error(`[BackupManager] S3 backup failed:`, error) - await fs.remove(backupedFilePath) - throw error - } - } - - async restoreFromS3(_: Electron.IpcMainInvokeEvent, s3Config: S3Config) { - const filename = s3Config.fileName || 'cherry-studio.backup.zip' - - // 只记录开始和结束或错误 - Logger.log(`[BackupManager] Starting restore from S3: ${filename}`) - - const s3Client = new S3Storage('s3', { - endpoint: s3Config.endpoint, - region: s3Config.region, - bucket: s3Config.bucket, - access_key_id: s3Config.access_key_id, - secret_access_key: s3Config.secret_access_key, - root: s3Config.root || '' - }) - try { - const retrievedFile = await s3Client.getFileContents(filename) - const backupedFilePath = path.join(this.backupDir, filename) - if (!fs.existsSync(this.backupDir)) { - fs.mkdirSync(this.backupDir, { recursive: true }) - } - await new Promise((resolve, reject) => { - const writeStream = fs.createWriteStream(backupedFilePath) - writeStream.write(retrievedFile as Buffer) - writeStream.end() - writeStream.on('finish', () => resolve()) - writeStream.on('error', (error) => reject(error)) - }) - - Logger.log(`[BackupManager] S3 restore file downloaded successfully: ${filename}`) - return await this.restore(_, backupedFilePath) - } catch (error: any) { - Logger.error('[BackupManager] Failed to restore from S3:', error) - throw new Error(error.message || 'Failed to restore backup file') - } - } - - listS3Files = async (_: Electron.IpcMainInvokeEvent, s3Config: S3Config) => { - try { - const s3Client = new S3Storage('s3', { - endpoint: s3Config.endpoint, - region: s3Config.region, - bucket: s3Config.bucket, - access_key_id: s3Config.access_key_id, - secret_access_key: s3Config.secret_access_key, - root: s3Config.root || '' - }) - const entries = await s3Client.instance?.list('/') - const files: Array<{ fileName: string; modifiedTime: string; size: number }> = [] - if (entries) { - for await (const entry of entries) { - const path = entry.path() - if (path.endsWith('.zip')) { - const meta = await s3Client.instance!.stat(path) - if (meta.isFile()) { - files.push({ - fileName: path.replace(/^\/+/, ''), - modifiedTime: meta.lastModified || '', - size: Number(meta.contentLength || 0n) - }) - } - } - } - } - return files.sort((a, b) => new Date(b.modifiedTime).getTime() - new Date(a.modifiedTime).getTime()) - } catch (error: any) { - Logger.error('Failed to list S3 files:', error) - throw new Error(error.message || 'Failed to list backup files') - } - } - - async deleteS3File(_: Electron.IpcMainInvokeEvent, fileName: string, s3Config: S3Config) { - try { - const s3Client = new S3Storage('s3', { - endpoint: s3Config.endpoint, - region: s3Config.region, - bucket: s3Config.bucket, - access_key_id: s3Config.access_key_id, - secret_access_key: s3Config.secret_access_key, - root: s3Config.root || '' - }) - return await s3Client.deleteFile(fileName) - } catch (error: any) { - Logger.error('Failed to delete S3 file:', error) - throw new Error(error.message || 'Failed to delete backup file') - } - } - - async checkS3Connection(_: Electron.IpcMainInvokeEvent, s3Config: S3Config) { - const s3Client = new S3Storage('s3', { - endpoint: s3Config.endpoint, - region: s3Config.region, - bucket: s3Config.bucket, - access_key_id: s3Config.access_key_id, - secret_access_key: s3Config.secret_access_key, - root: s3Config.root || '' - }) - return await s3Client.checkConnection() - } } export default BackupManager diff --git a/src/main/services/RemoteStorage.ts b/src/main/services/RemoteStorage.ts index 4efc57b6c6..b62489bbbe 100644 --- a/src/main/services/RemoteStorage.ts +++ b/src/main/services/RemoteStorage.ts @@ -1,83 +1,57 @@ -import Logger from 'electron-log' -import type { Operator as OperatorType } from 'opendal' -const { Operator } = require('opendal') +// import Logger from 'electron-log' +// import { Operator } from 'opendal' -export default class S3Storage { - public instance: OperatorType | undefined +// export default class RemoteStorage { +// public instance: Operator | undefined - /** - * - * @param scheme is the scheme for opendal services. Available value includes "azblob", "azdls", "cos", "gcs", "obs", "oss", "s3", "webdav", "webhdfs", "aliyun-drive", "alluxio", "azfile", "dropbox", "gdrive", "onedrive", "postgresql", "mysql", "redis", "swift", "mongodb", "alluxio", "b2", "seafile", "upyun", "koofr", "yandex-disk" - * @param options is the options for given opendal services. Valid options depend on the scheme. Checkout https://docs.rs/opendal/latest/opendal/services/index.html for all valid options. - * - * For example, use minio as remote storage: - * - * ```typescript - * const storage = new S3Storage('s3', { - * endpoint: 'http://localhost:9000', - * region: 'us-east-1', - * bucket: 'testbucket', - * access_key_id: 'user', - * secret_access_key: 'password', - * root: '/path/to/basepath', - * }) - * ``` - */ - constructor(scheme: string, options?: Record | undefined | null) { - this.instance = new Operator(scheme, options) +// /** +// * +// * @param scheme is the scheme for opendal services. Available value includes "azblob", "azdls", "cos", "gcs", "obs", "oss", "s3", "webdav", "webhdfs", "aliyun-drive", "alluxio", "azfile", "dropbox", "gdrive", "onedrive", "postgresql", "mysql", "redis", "swift", "mongodb", "alluxio", "b2", "seafile", "upyun", "koofr", "yandex-disk" +// * @param options is the options for given opendal services. Valid options depend on the scheme. Checkout https://docs.rs/opendal/latest/opendal/services/index.html for all valid options. +// * +// * For example, use minio as remote storage: +// * +// * ```typescript +// * const storage = new RemoteStorage('s3', { +// * endpoint: 'http://localhost:9000', +// * region: 'us-east-1', +// * bucket: 'testbucket', +// * access_key_id: 'user', +// * secret_access_key: 'password', +// * root: '/path/to/basepath', +// * }) +// * ``` +// */ +// constructor(scheme: string, options?: Record | undefined | null) { +// this.instance = new Operator(scheme, options) - this.putFileContents = this.putFileContents.bind(this) - this.getFileContents = this.getFileContents.bind(this) - } +// this.putFileContents = this.putFileContents.bind(this) +// this.getFileContents = this.getFileContents.bind(this) +// } - public putFileContents = async (filename: string, data: string | Buffer) => { - if (!this.instance) { - return new Error('RemoteStorage client not initialized') - } +// public putFileContents = async (filename: string, data: string | Buffer) => { +// if (!this.instance) { +// return new Error('RemoteStorage client not initialized') +// } - try { - return await this.instance.write(filename, data) - } catch (error) { - Logger.error('[RemoteStorage] Error putting file contents:', error) - throw error - } - } +// try { +// return await this.instance.write(filename, data) +// } catch (error) { +// Logger.error('[RemoteStorage] Error putting file contents:', error) +// throw error +// } +// } - public getFileContents = async (filename: string) => { - if (!this.instance) { - throw new Error('RemoteStorage client not initialized') - } +// public getFileContents = async (filename: string) => { +// if (!this.instance) { +// throw new Error('RemoteStorage client not initialized') +// } - try { - return await this.instance.read(filename) - } catch (error) { - Logger.error('[RemoteStorage] Error getting file contents:', error) - throw error - } - } - - public deleteFile = async (filename: string) => { - if (!this.instance) { - throw new Error('RemoteStorage client not initialized') - } - try { - return await this.instance.delete(filename) - } catch (error) { - Logger.error('[RemoteStorage] Error deleting file:', error) - throw error - } - } - - public checkConnection = async () => { - if (!this.instance) { - throw new Error('RemoteStorage client not initialized') - } - try { - // 检查根目录是否可访问 - return await this.instance.stat('/') - } catch (error) { - Logger.error('[RemoteStorage] Error checking connection:', error) - throw error - } - } -} +// try { +// return await this.instance.read(filename) +// } catch (error) { +// Logger.error('[RemoteStorage] Error getting file contents:', error) +// throw error +// } +// } +// } diff --git a/src/preload/index.ts b/src/preload/index.ts index f6e49ece10..8412e00bc3 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -2,16 +2,7 @@ import type { ExtractChunkData } from '@cherrystudio/embedjs-interfaces' import { electronAPI } from '@electron-toolkit/preload' import { UpgradeChannel } from '@shared/config/constant' import { IpcChannel } from '@shared/IpcChannel' -import { - FileType, - KnowledgeBaseParams, - KnowledgeItem, - MCPServer, - S3Config, - Shortcut, - ThemeMode, - WebDavConfig -} from '@types' +import { FileType, KnowledgeBaseParams, KnowledgeItem, MCPServer, Shortcut, ThemeMode, WebDavConfig } from '@types' import { contextBridge, ipcRenderer, OpenDialogOptions, shell, webUtils } from 'electron' import { Notification } from 'src/renderer/src/types/notification' import { CreateDirectoryOptions } from 'webdav' @@ -80,13 +71,7 @@ const api = { createDirectory: (webdavConfig: WebDavConfig, path: string, options?: CreateDirectoryOptions) => ipcRenderer.invoke(IpcChannel.Backup_CreateDirectory, webdavConfig, path, options), deleteWebdavFile: (fileName: string, webdavConfig: WebDavConfig) => - ipcRenderer.invoke(IpcChannel.Backup_DeleteWebdavFile, fileName, webdavConfig), - backupToS3: (data: string, s3Config: S3Config) => ipcRenderer.invoke(IpcChannel.Backup_BackupToS3, data, s3Config), - restoreFromS3: (s3Config: S3Config) => ipcRenderer.invoke(IpcChannel.Backup_RestoreFromS3, s3Config), - listS3Files: (s3Config: S3Config) => ipcRenderer.invoke(IpcChannel.Backup_ListS3Files, s3Config), - deleteS3File: (fileName: string, s3Config: S3Config) => - ipcRenderer.invoke(IpcChannel.Backup_DeleteS3File, fileName, s3Config), - checkS3Connection: (s3Config: S3Config) => ipcRenderer.invoke(IpcChannel.Backup_CheckS3Connection, s3Config) + ipcRenderer.invoke(IpcChannel.Backup_DeleteWebdavFile, fileName, webdavConfig) }, file: { select: (options?: OpenDialogOptions) => ipcRenderer.invoke(IpcChannel.File_Select, options), diff --git a/src/renderer/src/components/S3BackupManager.tsx b/src/renderer/src/components/S3BackupManager.tsx deleted file mode 100644 index ecc9ed88ef..0000000000 --- a/src/renderer/src/components/S3BackupManager.tsx +++ /dev/null @@ -1,298 +0,0 @@ -import { DeleteOutlined, ExclamationCircleOutlined, ReloadOutlined } from '@ant-design/icons' -import { restoreFromS3 } from '@renderer/services/BackupService' -import { formatFileSize } from '@renderer/utils' -import { Button, Modal, Table, Tooltip } from 'antd' -import dayjs from 'dayjs' -import { useCallback, useEffect, useState } from 'react' -import { useTranslation } from 'react-i18next' - -interface BackupFile { - fileName: string - modifiedTime: string - size: number -} - -interface S3Config { - endpoint: string - region: string - bucket: string - access_key_id: string - secret_access_key: string - root?: string -} - -interface S3BackupManagerProps { - visible: boolean - onClose: () => void - s3Config: { - endpoint?: string - region?: string - bucket?: string - access_key_id?: string - secret_access_key?: string - root?: string - } - restoreMethod?: (fileName: string) => Promise -} - -export function S3BackupManager({ visible, onClose, s3Config, restoreMethod }: S3BackupManagerProps) { - const [backupFiles, setBackupFiles] = useState([]) - const [loading, setLoading] = useState(false) - const [selectedRowKeys, setSelectedRowKeys] = useState([]) - const [deleting, setDeleting] = useState(false) - const [restoring, setRestoring] = useState(false) - const [pagination, setPagination] = useState({ - current: 1, - pageSize: 5, - total: 0 - }) - const { t } = useTranslation() - - const { endpoint, region, bucket, access_key_id, secret_access_key, root } = s3Config - - const fetchBackupFiles = useCallback(async () => { - if (!endpoint || !region || !bucket || !access_key_id || !secret_access_key) { - window.message.error(t('settings.data.s3.manager.config.incomplete')) - return - } - - setLoading(true) - try { - const files = await window.api.backup.listS3Files({ - endpoint, - region, - bucket, - access_key_id, - secret_access_key, - root - } as S3Config) - setBackupFiles(files) - setPagination((prev) => ({ - ...prev, - total: files.length - })) - } catch (error: any) { - window.message.error(t('settings.data.s3.manager.files.fetch.error', { message: error.message })) - } finally { - setLoading(false) - } - }, [endpoint, region, bucket, access_key_id, secret_access_key, root, t]) - - useEffect(() => { - if (visible) { - fetchBackupFiles() - setSelectedRowKeys([]) - setPagination((prev) => ({ - ...prev, - current: 1 - })) - } - }, [visible, fetchBackupFiles]) - - const handleTableChange = (pagination: any) => { - setPagination(pagination) - } - - const handleDeleteSelected = async () => { - if (selectedRowKeys.length === 0) { - window.message.warning(t('settings.data.s3.manager.select.warning')) - return - } - - if (!endpoint || !region || !bucket || !access_key_id || !secret_access_key) { - window.message.error(t('settings.data.s3.manager.config.incomplete')) - return - } - - window.modal.confirm({ - title: t('settings.data.s3.manager.delete.confirm.title'), - icon: , - content: t('settings.data.s3.manager.delete.confirm.multiple', { count: selectedRowKeys.length }), - okText: t('settings.data.s3.manager.delete.confirm.title'), - cancelText: t('common.cancel'), - centered: true, - onOk: async () => { - setDeleting(true) - try { - // 依次删除选中的文件 - for (const key of selectedRowKeys) { - await window.api.backup.deleteS3File(key.toString(), { - endpoint, - region, - bucket, - access_key_id, - secret_access_key, - root - } as S3Config) - } - window.message.success( - t('settings.data.s3.manager.delete.success.multiple', { count: selectedRowKeys.length }) - ) - setSelectedRowKeys([]) - await fetchBackupFiles() - } catch (error: any) { - window.message.error(t('settings.data.s3.manager.delete.error', { message: error.message })) - } finally { - setDeleting(false) - } - } - }) - } - - const handleDeleteSingle = async (fileName: string) => { - if (!endpoint || !region || !bucket || !access_key_id || !secret_access_key) { - window.message.error(t('settings.data.s3.manager.config.incomplete')) - return - } - - window.modal.confirm({ - title: t('settings.data.s3.manager.delete.confirm.title'), - icon: , - content: t('settings.data.s3.manager.delete.confirm.single', { fileName }), - okText: t('settings.data.s3.manager.delete.confirm.title'), - cancelText: t('common.cancel'), - centered: true, - onOk: async () => { - setDeleting(true) - try { - await window.api.backup.deleteS3File(fileName, { - endpoint, - region, - bucket, - access_key_id, - secret_access_key, - root - } as S3Config) - window.message.success(t('settings.data.s3.manager.delete.success.single')) - await fetchBackupFiles() - } catch (error: any) { - window.message.error(t('settings.data.s3.manager.delete.error', { message: error.message })) - } finally { - setDeleting(false) - } - } - }) - } - - const handleRestore = async (fileName: string) => { - if (!endpoint || !region || !bucket || !access_key_id || !secret_access_key) { - window.message.error(t('settings.data.s3.manager.config.incomplete')) - return - } - - window.modal.confirm({ - title: t('settings.data.s3.restore.confirm.title'), - icon: , - content: t('settings.data.s3.restore.confirm.content'), - okText: t('settings.data.s3.restore.confirm.ok'), - cancelText: t('settings.data.s3.restore.confirm.cancel'), - centered: true, - onOk: async () => { - setRestoring(true) - try { - await (restoreMethod || restoreFromS3)(fileName) - window.message.success(t('settings.data.s3.restore.success')) - onClose() // 关闭模态框 - } catch (error: any) { - window.message.error(t('settings.data.s3.restore.error', { message: error.message })) - } finally { - setRestoring(false) - } - } - }) - } - - const columns = [ - { - title: t('settings.data.s3.manager.columns.fileName'), - dataIndex: 'fileName', - key: 'fileName', - ellipsis: { - showTitle: false - }, - render: (fileName: string) => ( - - {fileName} - - ) - }, - { - title: t('settings.data.s3.manager.columns.modifiedTime'), - dataIndex: 'modifiedTime', - key: 'modifiedTime', - width: 180, - render: (time: string) => dayjs(time).format('YYYY-MM-DD HH:mm:ss') - }, - { - title: t('settings.data.s3.manager.columns.size'), - dataIndex: 'size', - key: 'size', - width: 120, - render: (size: number) => formatFileSize(size) - }, - { - title: t('settings.data.s3.manager.columns.actions'), - key: 'action', - width: 160, - render: (_: any, record: BackupFile) => ( - <> - - - - ) - } - ] - - const rowSelection = { - selectedRowKeys, - onChange: (selectedRowKeys: React.Key[]) => { - setSelectedRowKeys(selectedRowKeys) - } - } - - return ( - } onClick={fetchBackupFiles} disabled={loading}> - {t('settings.data.s3.manager.refresh')} - , - , - - ]}> - - - ) -} diff --git a/src/renderer/src/components/S3Modals.tsx b/src/renderer/src/components/S3Modals.tsx deleted file mode 100644 index a74ad2e9ca..0000000000 --- a/src/renderer/src/components/S3Modals.tsx +++ /dev/null @@ -1,258 +0,0 @@ -import { backupToS3, handleData } from '@renderer/services/BackupService' -import { formatFileSize } from '@renderer/utils' -import { Input, Modal, Select, Spin } from 'antd' -import dayjs from 'dayjs' -import { useCallback, useState } from 'react' -import { useTranslation } from 'react-i18next' - -interface BackupFile { - fileName: string - modifiedTime: string - size: number -} - -export function useS3BackupModal() { - const [customFileName, setCustomFileName] = useState('') - const [isModalVisible, setIsModalVisible] = useState(false) - const [backuping, setBackuping] = useState(false) - - const handleBackup = async () => { - setBackuping(true) - try { - await backupToS3({ customFileName, showMessage: true }) - } finally { - setBackuping(false) - setIsModalVisible(false) - } - } - - const handleCancel = () => { - setIsModalVisible(false) - } - - const showBackupModal = useCallback(async () => { - // 获取默认文件名 - const deviceType = await window.api.system.getDeviceType() - const hostname = await window.api.system.getHostname() - const timestamp = dayjs().format('YYYYMMDDHHmmss') - const defaultFileName = `cherry-studio.${timestamp}.${hostname}.${deviceType}.zip` - setCustomFileName(defaultFileName) - setIsModalVisible(true) - }, []) - - return { - isModalVisible, - handleBackup, - handleCancel, - backuping, - customFileName, - setCustomFileName, - showBackupModal - } -} - -type S3BackupModalProps = { - isModalVisible: boolean - handleBackup: () => Promise - handleCancel: () => void - backuping: boolean - customFileName: string - setCustomFileName: (value: string) => void -} - -export function S3BackupModal({ - isModalVisible, - handleBackup, - handleCancel, - backuping, - customFileName, - setCustomFileName -}: S3BackupModalProps) { - const { t } = useTranslation() - - return ( - - setCustomFileName(e.target.value)} - placeholder={t('settings.data.s3.backup.modal.filename.placeholder')} - /> - - ) -} - -interface UseS3RestoreModalProps { - endpoint: string | undefined - region: string | undefined - bucket: string | undefined - access_key_id: string | undefined - secret_access_key: string | undefined - root?: string | undefined -} - -export function useS3RestoreModal({ - endpoint, - region, - bucket, - access_key_id, - secret_access_key, - root -}: UseS3RestoreModalProps) { - const [isRestoreModalVisible, setIsRestoreModalVisible] = useState(false) - const [restoring, setRestoring] = useState(false) - const [selectedFile, setSelectedFile] = useState(null) - const [loadingFiles, setLoadingFiles] = useState(false) - const [backupFiles, setBackupFiles] = useState([]) - const { t } = useTranslation() - - const showRestoreModal = useCallback(async () => { - if (!endpoint || !region || !bucket || !access_key_id || !secret_access_key) { - window.message.error({ content: t('settings.data.s3.manager.config.incomplete'), key: 's3-error' }) - return - } - - setIsRestoreModalVisible(true) - setLoadingFiles(true) - try { - const files = await window.api.backup.listS3Files({ - endpoint, - region, - bucket, - access_key_id, - secret_access_key, - root - }) - setBackupFiles(files) - } catch (error: any) { - window.message.error({ - content: t('settings.data.s3.manager.files.fetch.error', { message: error.message }), - key: 'list-files-error' - }) - } finally { - setLoadingFiles(false) - } - }, [endpoint, region, bucket, access_key_id, secret_access_key, root, t]) - - const handleRestore = useCallback(async () => { - if (!selectedFile || !endpoint || !region || !bucket || !access_key_id || !secret_access_key) { - window.message.error({ - content: !selectedFile - ? t('settings.data.s3.restore.file.required') - : t('settings.data.s3.restore.config.incomplete'), - key: 'restore-error' - }) - return - } - - window.modal.confirm({ - title: t('settings.data.s3.restore.confirm.title'), - content: t('settings.data.s3.restore.confirm.content'), - okText: t('settings.data.s3.restore.confirm.ok'), - cancelText: t('settings.data.s3.restore.confirm.cancel'), - centered: true, - onOk: async () => { - setRestoring(true) - try { - const data = await window.api.backup.restoreFromS3({ - endpoint, - region, - bucket, - access_key_id, - secret_access_key, - root, - fileName: selectedFile - }) - await handleData(JSON.parse(data)) - window.message.success(t('settings.data.s3.restore.success')) - setIsRestoreModalVisible(false) - } catch (error: any) { - window.message.error({ - content: t('settings.data.s3.restore.error', { message: error.message }), - key: 'restore-error' - }) - } finally { - setRestoring(false) - } - } - }) - }, [selectedFile, endpoint, region, bucket, access_key_id, secret_access_key, root, t]) - - const handleCancel = () => { - setIsRestoreModalVisible(false) - } - - return { - isRestoreModalVisible, - handleRestore, - handleCancel, - restoring, - selectedFile, - setSelectedFile, - loadingFiles, - backupFiles, - showRestoreModal - } -} - -type S3RestoreModalProps = ReturnType - -export function S3RestoreModal({ - isRestoreModalVisible, - handleRestore, - handleCancel, - restoring, - selectedFile, - setSelectedFile, - loadingFiles, - backupFiles -}: S3RestoreModalProps) { - const { t } = useTranslation() - - return ( - -
- setEndpoint(e.target.value)} - style={{ width: 250 }} - type="url" - onBlur={() => dispatch(setS3({ ...s3, endpoint: endpoint || '' }))} - /> - - - - {t('settings.data.s3.region')} - setRegion(e.target.value)} - style={{ width: 250 }} - onBlur={() => dispatch(setS3({ ...s3, region: region || '' }))} - /> - - - - {t('settings.data.s3.bucket')} - setBucket(e.target.value)} - style={{ width: 250 }} - onBlur={() => dispatch(setS3({ ...s3, bucket: bucket || '' }))} - /> - - - - {t('settings.data.s3.accessKeyId')} - setAccessKeyId(e.target.value)} - style={{ width: 250 }} - onBlur={() => dispatch(setS3({ ...s3, accessKeyId: accessKeyId || '' }))} - /> - - - - {t('settings.data.s3.secretAccessKey')} - setSecretAccessKey(e.target.value)} - style={{ width: 250 }} - onBlur={() => dispatch(setS3({ ...s3, secretAccessKey: secretAccessKey || '' }))} - /> - - - - {t('settings.data.s3.root')} - setRoot(e.target.value)} - style={{ width: 250 }} - onBlur={() => dispatch(setS3({ ...s3, root: root || '' }))} - /> - - - - {t('settings.data.s3.backup.operation')} - - - - - - - - {t('settings.data.s3.autoSync')} - - - - - {t('settings.data.s3.maxBackups')} - - - - - {t('settings.data.s3.skipBackupFile')} - - - - {t('settings.data.s3.skipBackupFile.help')} - - {syncInterval > 0 && ( - <> - - - {t('settings.data.s3.syncStatus')} - {renderSyncStatus()} - - - )} - <> - - - - - - ) -} - -export default S3Settings diff --git a/src/renderer/src/services/BackupService.ts b/src/renderer/src/services/BackupService.ts index b99ea6c77e..3d78b2752a 100644 --- a/src/renderer/src/services/BackupService.ts +++ b/src/renderer/src/services/BackupService.ts @@ -4,62 +4,11 @@ import { upgradeToV7 } from '@renderer/databases/upgrades' import i18n from '@renderer/i18n' import store from '@renderer/store' import { setWebDAVSyncState } from '@renderer/store/backup' -import { setS3SyncState } from '@renderer/store/backup' import { uuid } from '@renderer/utils' import dayjs from 'dayjs' import { NotificationService } from './NotificationService' -// 重试删除S3文件的辅助函数 -async function deleteS3FileWithRetry(fileName: string, s3Config: any, maxRetries = 3) { - let lastError: Error | null = null - - for (let attempt = 1; attempt <= maxRetries; attempt++) { - try { - await window.api.backup.deleteS3File(fileName, s3Config) - Logger.log(`[Backup] Successfully deleted old backup file: ${fileName} (attempt ${attempt})`) - return true - } catch (error: any) { - lastError = error - Logger.warn(`[Backup] Delete attempt ${attempt}/${maxRetries} failed for ${fileName}:`, error.message) - - // 如果不是最后一次尝试,等待一段时间再重试 - if (attempt < maxRetries) { - const delay = attempt * 1000 + Math.random() * 1000 // 1-2秒的随机延迟 - await new Promise((resolve) => setTimeout(resolve, delay)) - } - } - } - - Logger.error(`[Backup] Failed to delete old backup file after ${maxRetries} attempts: ${fileName}`, lastError) - return false -} - -// 重试删除WebDAV文件的辅助函数 -async function deleteWebdavFileWithRetry(fileName: string, webdavConfig: any, maxRetries = 3) { - let lastError: Error | null = null - - for (let attempt = 1; attempt <= maxRetries; attempt++) { - try { - await window.api.backup.deleteWebdavFile(fileName, webdavConfig) - Logger.log(`[Backup] Successfully deleted old backup file: ${fileName} (attempt ${attempt})`) - return true - } catch (error: any) { - lastError = error - Logger.warn(`[Backup] Delete attempt ${attempt}/${maxRetries} failed for ${fileName}:`, error.message) - - // 如果不是最后一次尝试,等待一段时间再重试 - if (attempt < maxRetries) { - const delay = attempt * 1000 + Math.random() * 1000 // 1-2秒的随机延迟 - await new Promise((resolve) => setTimeout(resolve, delay)) - } - } - } - - Logger.error(`[Backup] Failed to delete old backup file after ${maxRetries} attempts: ${fileName}`, lastError) - return false -} - export async function backup(skipBackupFile: boolean) { const filename = `cherry-studio.${dayjs().format('YYYYMMDDHHmm')}.zip` const fileContnet = await getBackupData() @@ -212,21 +161,17 @@ export async function backupToWebdav({ // 文件已按修改时间降序排序,所以最旧的文件在末尾 const filesToDelete = currentDeviceFiles.slice(webdavMaxBackups) - Logger.log(`[Backup] Cleaning up ${filesToDelete.length} old backup files`) - - // 串行删除文件,避免并发请求导致的问题 - for (let i = 0; i < filesToDelete.length; i++) { - const file = filesToDelete[i] - await deleteWebdavFileWithRetry(file.fileName, { - webdavHost, - webdavUser, - webdavPass, - webdavPath - }) - - // 在删除操作之间添加短暂延迟,避免请求过于频繁 - if (i < filesToDelete.length - 1) { - await new Promise((resolve) => setTimeout(resolve, 500)) + 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) } } } @@ -297,201 +242,6 @@ export async function restoreFromWebdav(fileName?: string) { } } -// 备份到 S3 -export async function backupToS3({ - showMessage = false, - customFileName = '', - autoBackupProcess = false -}: { showMessage?: boolean; customFileName?: string; autoBackupProcess?: boolean } = {}) { - const notificationService = NotificationService.getInstance() - if (isManualBackupRunning) { - Logger.log('[Backup] Manual backup already in progress') - return - } - - // force set showMessage to false when auto backup process - if (autoBackupProcess) { - showMessage = false - } - - isManualBackupRunning = true - - store.dispatch(setS3SyncState({ syncing: true, lastSyncError: null })) - - const { - s3: { - endpoint: s3Endpoint, - region: s3Region, - bucket: s3Bucket, - accessKeyId: s3AccessKeyId, - secretAccessKey: s3SecretAccessKey, - root: s3Root, - maxBackups: s3MaxBackups, - skipBackupFile: s3SkipBackupFile - } - } = store.getState().settings - let deviceType = 'unknown' - let hostname = 'unknown' - try { - deviceType = (await window.api.system.getDeviceType()) || 'unknown' - hostname = (await window.api.system.getHostname()) || 'unknown' - } catch (error) { - Logger.error('[Backup] Failed to get device type or hostname:', error) - } - const timestamp = dayjs().format('YYYYMMDDHHmmss') - const backupFileName = customFileName || `cherry-studio.${timestamp}.${hostname}.${deviceType}.zip` - const finalFileName = backupFileName.endsWith('.zip') ? backupFileName : `${backupFileName}.zip` - const backupData = await getBackupData() - - // 上传文件 - try { - await window.api.backup.backupToS3(backupData, { - endpoint: s3Endpoint, - region: s3Region, - bucket: s3Bucket, - access_key_id: s3AccessKeyId, - secret_access_key: s3SecretAccessKey, - root: s3Root, - fileName: finalFileName, - skipBackupFile: s3SkipBackupFile - }) - - // S3上传成功 - store.dispatch( - setS3SyncState({ - lastSyncError: null - }) - ) - notificationService.send({ - id: uuid(), - type: 'success', - title: i18n.t('common.success'), - message: i18n.t('message.backup.success'), - silent: false, - timestamp: Date.now(), - source: 'backup' - }) - showMessage && window.message.success({ content: i18n.t('message.backup.success'), key: 'backup' }) - - // 清理旧备份文件 - if (s3MaxBackups > 0) { - try { - // 获取所有备份文件 - const files = await window.api.backup.listS3Files({ - endpoint: s3Endpoint, - region: s3Region, - bucket: s3Bucket, - access_key_id: s3AccessKeyId, - secret_access_key: s3SecretAccessKey, - root: s3Root - }) - - // 筛选当前设备的备份文件 - const currentDeviceFiles = files.filter((file) => { - // 检查文件名是否包含当前设备的标识信息 - return file.fileName.includes(deviceType) && file.fileName.includes(hostname) - }) - - // 如果当前设备的备份文件数量超过最大保留数量,删除最旧的文件 - if (currentDeviceFiles.length > s3MaxBackups) { - // 文件已按修改时间降序排序,所以最旧的文件在末尾 - const filesToDelete = currentDeviceFiles.slice(s3MaxBackups) - - Logger.log(`[Backup] Cleaning up ${filesToDelete.length} old backup files`) - - // 串行删除文件,避免并发请求导致的问题 - for (let i = 0; i < filesToDelete.length; i++) { - const file = filesToDelete[i] - await deleteS3FileWithRetry(file.fileName, { - endpoint: s3Endpoint, - region: s3Region, - bucket: s3Bucket, - access_key_id: s3AccessKeyId, - secret_access_key: s3SecretAccessKey, - root: s3Root - }) - - // 在删除操作之间添加短暂延迟,避免请求过于频繁 - if (i < filesToDelete.length - 1) { - await new Promise((resolve) => setTimeout(resolve, 500)) - } - } - } - } catch (error) { - Logger.error('[Backup] Failed to clean up old backup files:', error) - } - } - } catch (error: any) { - // if auto backup process, throw error - if (autoBackupProcess) { - throw error - } - notificationService.send({ - id: uuid(), - type: 'error', - title: i18n.t('message.backup.failed'), - message: error.message, - silent: false, - timestamp: Date.now(), - source: 'backup' - }) - store.dispatch(setS3SyncState({ lastSyncError: error.message })) - console.error('[Backup] backupToS3: Error uploading file to S3:', error) - showMessage && window.message.error({ content: i18n.t('message.backup.failed'), key: 'backup' }) - throw error - } finally { - if (!autoBackupProcess) { - store.dispatch( - setS3SyncState({ - lastSyncTime: Date.now(), - syncing: false - }) - ) - } - isManualBackupRunning = false - } -} - -// 从 S3 恢复 -export async function restoreFromS3(fileName?: string) { - const { - s3: { - endpoint: s3Endpoint, - region: s3Region, - bucket: s3Bucket, - accessKeyId: s3AccessKeyId, - secretAccessKey: s3SecretAccessKey, - root: s3Root - } - } = store.getState().settings - let data = '' - - try { - data = await window.api.backup.restoreFromS3({ - endpoint: s3Endpoint, - region: s3Region, - bucket: s3Bucket, - access_key_id: s3AccessKeyId, - secret_access_key: s3SecretAccessKey, - root: s3Root, - fileName - }) - } catch (error: any) { - console.error('[Backup] restoreFromS3: Error downloading file from S3:', error) - window.modal.error({ - title: i18n.t('message.restore.failed'), - content: error.message - }) - } - - try { - await handleData(JSON.parse(data)) - } catch (error) { - console.error('[Backup] Error downloading file from S3:', error) - window.message.error({ content: i18n.t('error.backup.file_format'), key: 'restore' }) - } -} - let autoSyncStarted = false let syncTimeout: NodeJS.Timeout | null = null let isAutoBackupRunning = false @@ -502,17 +252,9 @@ export function startAutoSync(immediate = false) { return } - const { - webdavAutoSync, - webdavHost, - s3: { autoSync: s3AutoSync, endpoint: s3Endpoint } - } = store.getState().settings + const { webdavAutoSync, webdavHost } = store.getState().settings - // 检查WebDAV或S3自动同步配置 - const hasWebdavConfig = webdavAutoSync && webdavHost - const hasS3Config = s3AutoSync && s3Endpoint - - if (!hasWebdavConfig && !hasS3Config) { + if (!webdavAutoSync || !webdavHost) { Logger.log('[AutoSync] Invalid sync settings, auto sync disabled') return } @@ -535,29 +277,22 @@ export function startAutoSync(immediate = false) { syncTimeout = null } - const { - webdavSyncInterval: _webdavSyncInterval, - s3: { syncInterval: _s3SyncInterval } - } = store.getState().settings - const { webdavSync, s3Sync } = store.getState().backup + const { webdavSyncInterval } = store.getState().settings + const { webdavSync } = store.getState().backup - // 使用当前激活的同步配置 - const syncInterval = hasWebdavConfig ? _webdavSyncInterval : _s3SyncInterval - const lastSyncTime = hasWebdavConfig ? webdavSync?.lastSyncTime : s3Sync?.lastSyncTime - - if (syncInterval <= 0) { + if (webdavSyncInterval <= 0) { Logger.log('[AutoSync] Invalid sync interval, auto sync disabled') stopAutoSync() return } // 用户指定的自动备份时间间隔(毫秒) - const requiredInterval = syncInterval * 60 * 1000 + const requiredInterval = webdavSyncInterval * 60 * 1000 let timeUntilNextSync = 1000 //also immediate switch (type) { - case 'fromLastSyncTime': // 如果存在最后一次同步的时间,以它为参考计算下一次同步的时间 - timeUntilNextSync = Math.max(1000, (lastSyncTime || 0) + requiredInterval - Date.now()) + case 'fromLastSyncTime': // 如果存在最后一次同步WebDAV的时间,以它为参考计算下一次同步的时间 + timeUntilNextSync = Math.max(1000, (webdavSync?.lastSyncTime || 0) + requiredInterval - Date.now()) break case 'fromNow': timeUntilNextSync = requiredInterval @@ -566,9 +301,8 @@ export function startAutoSync(immediate = false) { syncTimeout = setTimeout(performAutoBackup, timeUntilNextSync) - const backupType = hasWebdavConfig ? 'WebDAV' : 'S3' Logger.log( - `[AutoSync] Next ${backupType} sync scheduled in ${Math.floor(timeUntilNextSync / 1000 / 60)} minutes ${Math.floor( + `[AutoSync] Next sync scheduled in ${Math.floor(timeUntilNextSync / 1000 / 60)} minutes ${Math.floor( (timeUntilNextSync / 1000) % 60 )} seconds` ) @@ -587,28 +321,17 @@ export function startAutoSync(immediate = false) { while (retryCount < maxRetries) { try { - const backupType = hasWebdavConfig ? 'WebDAV' : 'S3' - Logger.log(`[AutoSync] Starting auto ${backupType} backup... (attempt ${retryCount + 1}/${maxRetries})`) + Logger.log(`[AutoSync] Starting auto backup... (attempt ${retryCount + 1}/${maxRetries})`) - if (hasWebdavConfig) { - await backupToWebdav({ autoBackupProcess: true }) - store.dispatch( - setWebDAVSyncState({ - lastSyncError: null, - lastSyncTime: Date.now(), - syncing: false - }) - ) - } else if (hasS3Config) { - await backupToS3({ autoBackupProcess: true }) - store.dispatch( - setS3SyncState({ - lastSyncError: null, - lastSyncTime: Date.now(), - syncing: false - }) - ) - } + await backupToWebdav({ autoBackupProcess: true }) + + store.dispatch( + setWebDAVSyncState({ + lastSyncError: null, + lastSyncTime: Date.now(), + syncing: false + }) + ) isAutoBackupRunning = false scheduleNextBackup() @@ -617,31 +340,20 @@ export function startAutoSync(immediate = false) { } catch (error: any) { retryCount++ if (retryCount === maxRetries) { - const backupType = hasWebdavConfig ? 'WebDAV' : 'S3' - Logger.error(`[AutoSync] Auto ${backupType} backup failed after all retries:`, error) + Logger.error('[AutoSync] Auto backup failed after all retries:', error) - if (hasWebdavConfig) { - store.dispatch( - setWebDAVSyncState({ - lastSyncError: 'Auto backup failed', - lastSyncTime: Date.now(), - syncing: false - }) - ) - } else if (hasS3Config) { - store.dispatch( - setS3SyncState({ - lastSyncError: 'Auto backup failed', - lastSyncTime: Date.now(), - syncing: false - }) - ) - } + store.dispatch( + setWebDAVSyncState({ + lastSyncError: 'Auto backup failed', + lastSyncTime: Date.now(), + syncing: false + }) + ) //only show 1 time error modal, and autoback stopped until user click ok await window.modal.error({ title: i18n.t('message.backup.failed'), - content: `[${backupType} Auto Backup] ${new Date().toLocaleString()} ` + error.message + content: `[WebDAV Auto Backup] ${new Date().toLocaleString()} ` + error.message }) scheduleNextBackup('fromNow') diff --git a/src/renderer/src/store/backup.ts b/src/renderer/src/store/backup.ts index 0740032efb..a8b7d342c5 100644 --- a/src/renderer/src/store/backup.ts +++ b/src/renderer/src/store/backup.ts @@ -8,7 +8,6 @@ export interface WebDAVSyncState { export interface BackupState { webdavSync: WebDAVSyncState - s3Sync: WebDAVSyncState } const initialState: BackupState = { @@ -16,11 +15,6 @@ const initialState: BackupState = { lastSyncTime: null, syncing: false, lastSyncError: null - }, - s3Sync: { - lastSyncTime: null, - syncing: false, - lastSyncError: null } } @@ -30,12 +24,9 @@ const backupSlice = createSlice({ reducers: { setWebDAVSyncState: (state, action: PayloadAction>) => { state.webdavSync = { ...state.webdavSync, ...action.payload } - }, - setS3SyncState: (state, action: PayloadAction>) => { - state.s3Sync = { ...state.s3Sync, ...action.payload } } } }) -export const { setWebDAVSyncState, setS3SyncState } = backupSlice.actions +export const { setWebDAVSyncState } = backupSlice.actions export default backupSlice.reducer diff --git a/src/renderer/src/store/settings.ts b/src/renderer/src/store/settings.ts index 8afbafc2a7..7d8e14ed11 100644 --- a/src/renderer/src/store/settings.ts +++ b/src/renderer/src/store/settings.ts @@ -37,19 +37,6 @@ export type UserTheme = { colorPrimary: string } -export interface S3Config { - endpoint: string - region: string - bucket: string - accessKeyId: string - secretAccessKey: string - root: string - autoSync: boolean - syncInterval: number - maxBackups: number - skipBackupFile: boolean -} - export interface SettingsState { showAssistants: boolean showTopics: boolean @@ -198,7 +185,6 @@ export interface SettingsState { knowledgeEmbed: boolean } defaultPaintingProvider: PaintingProvider - s3: S3Config } export type MultiModelMessageStyle = 'horizontal' | 'vertical' | 'fold' | 'grid' @@ -343,19 +329,7 @@ export const initialState: SettingsState = { backup: false, knowledgeEmbed: false }, - defaultPaintingProvider: 'aihubmix', - s3: { - endpoint: '', - region: '', - bucket: '', - accessKeyId: '', - secretAccessKey: '', - root: '', - autoSync: false, - syncInterval: 0, - maxBackups: 0, - skipBackupFile: false - } + defaultPaintingProvider: 'aihubmix' } const settingsSlice = createSlice({ @@ -719,9 +693,6 @@ const settingsSlice = createSlice({ }, setDefaultPaintingProvider: (state, action: PayloadAction) => { state.defaultPaintingProvider = action.payload - }, - setS3: (state, action: PayloadAction) => { - state.s3 = action.payload } } }) @@ -830,8 +801,7 @@ export const { setOpenAISummaryText, setOpenAIServiceTier, setNotificationSettings, - setDefaultPaintingProvider, - setS3 + setDefaultPaintingProvider } = settingsSlice.actions export default settingsSlice.reducer diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index 448f04c647..3b4cc5cdc3 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -730,16 +730,4 @@ export interface StoreSyncAction { export type OpenAISummaryText = 'auto' | 'concise' | 'detailed' | 'off' export type OpenAIServiceTier = 'auto' | 'default' | 'flex' - -export type S3Config = { - endpoint: string - region: string - bucket: string - access_key_id: string - secret_access_key: string - root?: string - fileName?: string - skipBackupFile?: boolean -} - export type { Message } from './newMessage' diff --git a/yarn.lock b/yarn.lock index 49b5b8fb84..aaf7a9f457 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3198,55 +3198,6 @@ __metadata: languageName: node linkType: hard -"@opendal/lib-darwin-arm64@npm:0.47.11": - version: 0.47.11 - resolution: "@opendal/lib-darwin-arm64@npm:0.47.11" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - -"@opendal/lib-darwin-x64@npm:0.47.11": - version: 0.47.11 - resolution: "@opendal/lib-darwin-x64@npm:0.47.11" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"@opendal/lib-linux-arm64-gnu@npm:0.47.11": - version: 0.47.11 - resolution: "@opendal/lib-linux-arm64-gnu@npm:0.47.11" - conditions: os=linux & cpu=arm64 & libc=glibc - languageName: node - linkType: hard - -"@opendal/lib-linux-arm64-musl@npm:0.47.11": - version: 0.47.11 - resolution: "@opendal/lib-linux-arm64-musl@npm:0.47.11" - conditions: os=linux & cpu=arm64 & libc=glibc - languageName: node - linkType: hard - -"@opendal/lib-linux-x64-gnu@npm:0.47.11": - version: 0.47.11 - resolution: "@opendal/lib-linux-x64-gnu@npm:0.47.11" - conditions: os=linux & cpu=x64 & libc=glibc - languageName: node - linkType: hard - -"@opendal/lib-win32-arm64-msvc@npm:0.47.11": - version: 0.47.11 - resolution: "@opendal/lib-win32-arm64-msvc@npm:0.47.11" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - -"@opendal/lib-win32-x64-msvc@npm:0.47.11": - version: 0.47.11 - resolution: "@opendal/lib-win32-x64-msvc@npm:0.47.11" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - "@parcel/watcher-android-arm64@npm:2.5.1": version: 2.5.1 resolution: "@parcel/watcher-android-arm64@npm:2.5.1" @@ -5761,7 +5712,6 @@ __metadata: npx-scope-finder: "npm:^1.2.0" officeparser: "npm:^4.1.1" openai: "patch:openai@npm%3A5.1.0#~/.yarn/patches/openai-npm-5.1.0-0e7b3ccb07.patch" - opendal: "npm:0.47.11" os-proxy-config: "npm:^1.1.2" p-queue: "npm:^8.1.0" playwright: "npm:^1.52.0" @@ -14246,36 +14196,6 @@ __metadata: languageName: node linkType: hard -"opendal@npm:0.47.11": - version: 0.47.11 - resolution: "opendal@npm:0.47.11" - dependencies: - "@opendal/lib-darwin-arm64": "npm:0.47.11" - "@opendal/lib-darwin-x64": "npm:0.47.11" - "@opendal/lib-linux-arm64-gnu": "npm:0.47.11" - "@opendal/lib-linux-arm64-musl": "npm:0.47.11" - "@opendal/lib-linux-x64-gnu": "npm:0.47.11" - "@opendal/lib-win32-arm64-msvc": "npm:0.47.11" - "@opendal/lib-win32-x64-msvc": "npm:0.47.11" - dependenciesMeta: - "@opendal/lib-darwin-arm64": - optional: true - "@opendal/lib-darwin-x64": - optional: true - "@opendal/lib-linux-arm64-gnu": - optional: true - "@opendal/lib-linux-arm64-musl": - optional: true - "@opendal/lib-linux-x64-gnu": - optional: true - "@opendal/lib-win32-arm64-msvc": - optional: true - "@opendal/lib-win32-x64-msvc": - optional: true - checksum: 10c0/0783da2651bb27ac693ce38938d12b00124530fb965364517eef3de17b3ff898cdecf06260a79a7d70745d57c2ba952a753a4bab52e0831aa7232c3a69120225 - languageName: node - linkType: hard - "option@npm:~0.2.1": version: 0.2.4 resolution: "option@npm:0.2.4"