diff --git a/src/main/services/BackupManager.ts b/src/main/services/BackupManager.ts index 6087cb6a2a..2b607cb51a 100644 --- a/src/main/services/BackupManager.ts +++ b/src/main/services/BackupManager.ts @@ -321,14 +321,22 @@ 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, undefined, webdavConfig.skipBackupFile) - const contentLength = (await fs.stat(backupedFilePath)).size const webdavClient = new WebDav(webdavConfig) try { - const result = await webdavClient.putFileContents(filename, fs.createReadStream(backupedFilePath), { - overwrite: true, - contentLength - }) - // 上传成功后删除本地备份文件 + let result + if (webdavConfig.disableStream) { + const fileContent = await fs.readFile(backupedFilePath) + result = await webdavClient.putFileContents(filename, fileContent, { + overwrite: true + }) + } else { + const contentLength = (await fs.stat(backupedFilePath)).size + result = await webdavClient.putFileContents(filename, fs.createReadStream(backupedFilePath), { + overwrite: true, + contentLength + }) + } + await fs.remove(backupedFilePath) return result } catch (error) { diff --git a/src/renderer/src/components/WebdavBackupManager.tsx b/src/renderer/src/components/WebdavBackupManager.tsx index a434e3c63d..f0f2930686 100644 --- a/src/renderer/src/components/WebdavBackupManager.tsx +++ b/src/renderer/src/components/WebdavBackupManager.tsx @@ -27,6 +27,7 @@ interface WebdavBackupManagerProps { webdavUser?: string webdavPass?: string webdavPath?: string + webdavDisableStream?: boolean } restoreMethod?: (fileName: string) => Promise } diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 301f97505f..db307f3b8f 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -1676,7 +1676,11 @@ "syncError": "Backup Error", "syncStatus": "Backup Status", "title": "WebDAV", - "user": "WebDAV User" + "user": "WebDAV User", + "disableStream": { + "title": "Disable Stream Upload", + "help": "When enabled, loads the file into memory before uploading. This can solve incompatibility issues with some WebDAV servers that do not support chunked uploads, but it will increase memory usage." + } }, "yuque": { "check": { diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index 13f55bac96..db0ef19f87 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -1676,7 +1676,11 @@ "syncError": "バックアップエラー", "syncStatus": "バックアップ状態", "title": "WebDAV", - "user": "WebDAVユーザー" + "user": "WebDAVユーザー", + "disableStream": { + "title": "ストリーミングアップロードを無効にする", + "help": "有効にすると、アップロード前にファイルがメモリに読み込まれます。これにより、チャンクアップロードをサポートしていない一部のWebDAVサーバーとの互換性の問題を解決できますが、メモリ使用量が増加します。" + } }, "yuque": { "check": { diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index a59b9d6581..476d4a8836 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -1676,7 +1676,11 @@ "syncError": "Ошибка резервного копирования", "syncStatus": "Статус резервного копирования", "title": "WebDAV", - "user": "Пользователь WebDAV" + "user": "Пользователь WebDAV", + "disableStream": { + "title": "Отключить потоковую загрузку", + "help": "При включении файл загружается в память перед отправкой. Это может решить проблемы совместимости с некоторыми серверами WebDAV, не поддерживающими фрагментированную (chunked) загрузку, но увеличит потребление памяти." + } }, "yuque": { "check": { diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 52fea95ad0..97c5588786 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -1676,7 +1676,11 @@ "syncError": "备份错误", "syncStatus": "备份状态", "title": "WebDAV", - "user": "WebDAV 用户名" + "user": "WebDAV 用户名", + "disableStream": { + "title": "禁用流式上传", + "help": "开启后,将文件加载到内存中再上传,可解决部分WebDAV服务不兼容chunked上传的问题,但会增加内存占用。" + } }, "yuque": { "check": { diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 21c937d2d5..621dc137e6 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -1676,7 +1676,11 @@ "syncError": "備份錯誤", "syncStatus": "備份狀態", "title": "WebDAV", - "user": "WebDAV 使用者名稱" + "user": "WebDAV 使用者名稱", + "disableStream": { + "title": "禁用串流上傳", + "help": "開啟後,將檔案載入到記憶體中再上傳,可解決部分 WebDAV 服務不相容 chunked 上傳的問題,但會增加記憶體佔用。" + } }, "yuque": { "check": { diff --git a/src/renderer/src/pages/settings/DataSettings/WebDavSettings.tsx b/src/renderer/src/pages/settings/DataSettings/WebDavSettings.tsx index 7f42c9fe30..a791922a4d 100644 --- a/src/renderer/src/pages/settings/DataSettings/WebDavSettings.tsx +++ b/src/renderer/src/pages/settings/DataSettings/WebDavSettings.tsx @@ -9,6 +9,7 @@ import { startAutoSync, stopAutoSync } from '@renderer/services/BackupService' import { useAppDispatch, useAppSelector } from '@renderer/store' import { setWebdavAutoSync, + setWebdavDisableStream as _setWebdavDisableStream, setWebdavHost as _setWebdavHost, setWebdavMaxBackups as _setWebdavMaxBackups, setWebdavPass as _setWebdavPass, @@ -32,7 +33,8 @@ const WebDavSettings: FC = () => { webdavPath: webDAVPath, webdavSyncInterval: webDAVSyncInterval, webdavMaxBackups: webDAVMaxBackups, - webdavSkipBackupFile: webdDAVSkipBackupFile + webdavSkipBackupFile: webdDAVSkipBackupFile, + webdavDisableStream: webDAVDisableStream } = useSettings() const [webdavHost, setWebdavHost] = useState(webDAVHost) @@ -40,6 +42,7 @@ const WebDavSettings: FC = () => { const [webdavPass, setWebdavPass] = useState(webDAVPass) const [webdavPath, setWebdavPath] = useState(webDAVPath) const [webdavSkipBackupFile, setWebdavSkipBackupFile] = useState(webdDAVSkipBackupFile) + const [webdavDisableStream, setWebdavDisableStream] = useState(webDAVDisableStream) const [backupManagerVisible, setBackupManagerVisible] = useState(false) const [syncInterval, setSyncInterval] = useState(webDAVSyncInterval) @@ -76,6 +79,11 @@ const WebDavSettings: FC = () => { dispatch(_setWebdavSkipBackupFile(value)) } + const onDisableStreamChange = (value: boolean) => { + setWebdavDisableStream(value) + dispatch(_setWebdavDisableStream(value)) + } + const renderSyncStatus = () => { if (!webdavHost) return null @@ -220,6 +228,14 @@ const WebDavSettings: FC = () => { {t('settings.data.backup.skip_file_data_help')} + + + {t('settings.data.webdav.disableStream.title')} + + + + {t('settings.data.webdav.disableStream.help')} + {webdavSync && syncInterval > 0 && ( <> @@ -246,7 +262,8 @@ const WebDavSettings: FC = () => { webdavHost, webdavUser, webdavPass, - webdavPath + webdavPath, + webdavDisableStream }} /> diff --git a/src/renderer/src/services/BackupService.ts b/src/renderer/src/services/BackupService.ts index 74cd253679..a165e9f27a 100644 --- a/src/renderer/src/services/BackupService.ts +++ b/src/renderer/src/services/BackupService.ts @@ -154,8 +154,15 @@ export async function backupToWebdav({ store.dispatch(setWebDAVSyncState({ syncing: true, lastSyncError: null })) - const { webdavHost, webdavUser, webdavPass, webdavPath, webdavMaxBackups, webdavSkipBackupFile } = - store.getState().settings + const { + webdavHost, + webdavUser, + webdavPass, + webdavPath, + webdavMaxBackups, + webdavSkipBackupFile, + webdavDisableStream + } = store.getState().settings let deviceType = 'unknown' let hostname = 'unknown' try { @@ -177,7 +184,8 @@ export async function backupToWebdav({ webdavPass, webdavPath, fileName: finalFileName, - skipBackupFile: webdavSkipBackupFile + skipBackupFile: webdavSkipBackupFile, + disableStream: webdavDisableStream }) if (success) { store.dispatch( diff --git a/src/renderer/src/store/migrate.ts b/src/renderer/src/store/migrate.ts index 48eb12b039..0bb1d9f6fe 100644 --- a/src/renderer/src/store/migrate.ts +++ b/src/renderer/src/store/migrate.ts @@ -1799,6 +1799,16 @@ const migrateConfig = { } catch (error) { return state } + }, + '122': (state: RootState) => { + try { + if (state.settings && typeof state.settings.webdavDisableStream === 'undefined') { + state.settings.webdavDisableStream = false + } + return state + } catch (error) { + return state + } } } diff --git a/src/renderer/src/store/settings.ts b/src/renderer/src/store/settings.ts index be1fe8a794..1d30d9d9fa 100644 --- a/src/renderer/src/store/settings.ts +++ b/src/renderer/src/store/settings.ts @@ -112,6 +112,7 @@ export interface SettingsState { webdavSyncInterval: number webdavMaxBackups: number webdavSkipBackupFile: boolean + webdavDisableStream: boolean translateModelPrompt: string autoTranslateWithSpace: boolean showTranslateConfirm: boolean @@ -273,6 +274,7 @@ export const initialState: SettingsState = { webdavSyncInterval: 0, webdavMaxBackups: 0, webdavSkipBackupFile: false, + webdavDisableStream: false, translateModelPrompt: TRANSLATE_PROMPT, autoTranslateWithSpace: false, showTranslateConfirm: true, @@ -501,6 +503,9 @@ const settingsSlice = createSlice({ setWebdavSkipBackupFile: (state, action: PayloadAction) => { state.webdavSkipBackupFile = action.payload }, + setWebdavDisableStream: (state, action: PayloadAction) => { + state.webdavDisableStream = action.payload + }, setCodeExecution: (state, action: PayloadAction<{ enabled?: boolean; timeoutMinutes?: number }>) => { if (action.payload.enabled !== undefined) { state.codeExecution.enabled = action.payload.enabled @@ -801,6 +806,7 @@ export const { setWebdavSyncInterval, setWebdavMaxBackups, setWebdavSkipBackupFile, + setWebdavDisableStream, setCodeExecution, setCodeEditor, setCodePreview, diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index 3d6fb827b6..633a17dd1d 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -356,6 +356,7 @@ export type WebDavConfig = { webdavPath?: string fileName?: string skipBackupFile?: boolean + disableStream?: boolean } export type AppInfo = {