mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-24 10:40:07 +08:00
fix: 修复数据目录迁移的bug (#7386)
* fix: move initAppDataDir function inline and remove export from utils/file.ts * fix some bugs * fix shouldcopy error * fix: handle appDataPath initialization and update logic in file.ts; update defaultChecked in DataSettings component * fix: improve appDataPath handling and migration logic in file.ts * fix: add error message for selecting the same app data path in DataSettings component and update localization files * fix: ensure migration confirmation modal is shown correctly in DataSettings component * feat: add new IPC channel for retrieving data path from arguments and update related components for migration handling * fix: update app data path validation to check for prefix match in DataSettings component * refactor: simplify data migration logic in DataSettings component by removing unnecessary flag * fix: update initAppDataDir invocation to check for app packaging status in bootstrap.ts
This commit is contained in:
parent
60b37876b1
commit
c660aaba3d
@ -20,6 +20,9 @@ export enum IpcChannel {
|
||||
App_Copy = 'app:copy',
|
||||
App_SetStopQuitApp = 'app:set-stop-quit-app',
|
||||
App_SetAppDataPath = 'app:set-app-data-path',
|
||||
App_GetDataPathFromArgs = 'app:get-data-path-from-args',
|
||||
App_FlushAppData = 'app:flush-app-data',
|
||||
App_IsNotEmptyDir = 'app:is-not-empty-dir',
|
||||
App_RelaunchApp = 'app:relaunch-app',
|
||||
App_IsBinaryExist = 'app:is-binary-exist',
|
||||
App_GetBinaryPath = 'app:get-binary-path',
|
||||
|
||||
5
src/main/bootstrap.ts
Normal file
5
src/main/bootstrap.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { app } from 'electron'
|
||||
|
||||
import { initAppDataDir } from './utils/file'
|
||||
|
||||
app.isPackaged && initAppDataDir()
|
||||
@ -1,7 +1,11 @@
|
||||
// don't reorder this file, it's used to initialize the app data dir and
|
||||
// other which should be run before the main process is ready
|
||||
// eslint-disable-next-line
|
||||
import './bootstrap'
|
||||
|
||||
import '@main/config'
|
||||
|
||||
import { electronApp, optimizer } from '@electron-toolkit/utils'
|
||||
import { initAppDataDir } from '@main/utils/file'
|
||||
import { replaceDevtoolsFont } from '@main/utils/windowUtil'
|
||||
import { app } from 'electron'
|
||||
import installExtension, { REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS } from 'electron-devtools-installer'
|
||||
@ -22,7 +26,6 @@ import { registerShortcuts } from './services/ShortcutService'
|
||||
import { TrayService } from './services/TrayService'
|
||||
import { windowService } from './services/WindowService'
|
||||
|
||||
initAppDataDir()
|
||||
Logger.initialize()
|
||||
|
||||
/**
|
||||
|
||||
@ -34,7 +34,7 @@ import { setOpenLinkExternal } from './services/WebviewService'
|
||||
import { windowService } from './services/WindowService'
|
||||
import { calculateDirectorySize, getResourcePath } from './utils'
|
||||
import { decrypt, encrypt } from './utils/aes'
|
||||
import { getCacheDir, getConfigDir, getFilesDir, hasWritePermission, updateConfig } from './utils/file'
|
||||
import { getCacheDir, getConfigDir, getFilesDir, hasWritePermission, updateAppDataConfig } from './utils/file'
|
||||
import { compress, decompress } from './utils/zip'
|
||||
|
||||
const fileManager = new FileStorage()
|
||||
@ -218,10 +218,28 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
||||
|
||||
// Set app data path
|
||||
ipcMain.handle(IpcChannel.App_SetAppDataPath, async (_, filePath: string) => {
|
||||
updateConfig(filePath)
|
||||
updateAppDataConfig(filePath)
|
||||
app.setPath('userData', filePath)
|
||||
})
|
||||
|
||||
ipcMain.handle(IpcChannel.App_GetDataPathFromArgs, () => {
|
||||
return process.argv
|
||||
.slice(1)
|
||||
.find((arg) => arg.startsWith('--new-data-path='))
|
||||
?.split('--new-data-path=')[1]
|
||||
})
|
||||
|
||||
ipcMain.handle(IpcChannel.App_FlushAppData, () => {
|
||||
BrowserWindow.getAllWindows().forEach((w) => {
|
||||
w.webContents.session.flushStorageData()
|
||||
w.webContents.session.cookies.flushStore()
|
||||
})
|
||||
})
|
||||
|
||||
ipcMain.handle(IpcChannel.App_IsNotEmptyDir, async (_, path: string) => {
|
||||
return fs.readdirSync(path).length > 0
|
||||
})
|
||||
|
||||
// Copy user data to new location
|
||||
ipcMain.handle(IpcChannel.App_Copy, async (_, oldPath: string, newPath: string) => {
|
||||
try {
|
||||
@ -234,8 +252,8 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
||||
})
|
||||
|
||||
// Relaunch app
|
||||
ipcMain.handle(IpcChannel.App_RelaunchApp, () => {
|
||||
app.relaunch()
|
||||
ipcMain.handle(IpcChannel.App_RelaunchApp, (_, options?: Electron.RelaunchOptions) => {
|
||||
app.relaunch(options)
|
||||
app.exit(0)
|
||||
})
|
||||
|
||||
|
||||
@ -8,6 +8,20 @@ import { FileType, FileTypes } from '@types'
|
||||
import { app } from 'electron'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
export function initAppDataDir() {
|
||||
const appDataPath = getAppDataPathFromConfig()
|
||||
if (appDataPath) {
|
||||
app.setPath('userData', appDataPath)
|
||||
return
|
||||
}
|
||||
|
||||
if (isPortable) {
|
||||
const portableDir = process.env.PORTABLE_EXECUTABLE_DIR
|
||||
app.setPath('userData', path.join(portableDir || app.getPath('exe'), 'data'))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 创建文件类型映射表,提高查找效率
|
||||
const fileTypeMap = new Map<string, FileTypes>()
|
||||
|
||||
@ -35,46 +49,70 @@ export function hasWritePermission(path: string) {
|
||||
function getAppDataPathFromConfig() {
|
||||
try {
|
||||
const configPath = path.join(getConfigDir(), 'config.json')
|
||||
if (fs.existsSync(configPath)) {
|
||||
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'))
|
||||
if (config.appDataPath && fs.existsSync(config.appDataPath) && hasWritePermission(config.appDataPath)) {
|
||||
return config.appDataPath
|
||||
}
|
||||
if (!fs.existsSync(configPath)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'))
|
||||
|
||||
if (!config.appDataPath) {
|
||||
return null
|
||||
}
|
||||
|
||||
let appDataPath = null
|
||||
// 兼容旧版本
|
||||
if (config.appDataPath && typeof config.appDataPath === 'string') {
|
||||
appDataPath = config.appDataPath
|
||||
// 将旧版本数据迁移到新版本
|
||||
appDataPath && updateAppDataConfig(appDataPath)
|
||||
} else {
|
||||
appDataPath = config.appDataPath.find(
|
||||
(item: { executablePath: string }) => item.executablePath === app.getPath('exe')
|
||||
)?.dataPath
|
||||
}
|
||||
|
||||
if (appDataPath && fs.existsSync(appDataPath) && hasWritePermission(appDataPath)) {
|
||||
return appDataPath
|
||||
}
|
||||
|
||||
return null
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export function initAppDataDir() {
|
||||
const appDataPath = getAppDataPathFromConfig()
|
||||
if (appDataPath) {
|
||||
app.setPath('userData', appDataPath)
|
||||
return
|
||||
}
|
||||
|
||||
if (isPortable) {
|
||||
const portableDir = process.env.PORTABLE_EXECUTABLE_DIR
|
||||
app.setPath('userData', path.join(portableDir || app.getPath('exe'), 'data'))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
export function updateConfig(appDataPath: string) {
|
||||
export function updateAppDataConfig(appDataPath: string) {
|
||||
const configDir = getConfigDir()
|
||||
if (!fs.existsSync(configDir)) {
|
||||
fs.mkdirSync(configDir, { recursive: true })
|
||||
}
|
||||
|
||||
// config.json
|
||||
// appDataPath: [{ executablePath: string, dataPath: string }]
|
||||
const configPath = path.join(getConfigDir(), 'config.json')
|
||||
if (!fs.existsSync(configPath)) {
|
||||
fs.writeFileSync(configPath, JSON.stringify({ appDataPath }, null, 2))
|
||||
fs.writeFileSync(
|
||||
configPath,
|
||||
JSON.stringify({ appDataPath: [{ executablePath: app.getPath('exe'), dataPath: appDataPath }] }, null, 2)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'))
|
||||
config.appDataPath = appDataPath
|
||||
if (!config.appDataPath || (config.appDataPath && typeof config.appDataPath !== 'object')) {
|
||||
config.appDataPath = []
|
||||
}
|
||||
|
||||
const existingPath = config.appDataPath.find(
|
||||
(item: { executablePath: string }) => item.executablePath === app.getPath('exe')
|
||||
)
|
||||
|
||||
if (existingPath) {
|
||||
existingPath.dataPath = appDataPath
|
||||
} else {
|
||||
config.appDataPath.push({ executablePath: app.getPath('exe'), dataPath: appDataPath })
|
||||
}
|
||||
|
||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2))
|
||||
}
|
||||
|
||||
|
||||
@ -29,9 +29,12 @@ const api = {
|
||||
select: (options: Electron.OpenDialogOptions) => ipcRenderer.invoke(IpcChannel.App_Select, options),
|
||||
hasWritePermission: (path: string) => ipcRenderer.invoke(IpcChannel.App_HasWritePermission, path),
|
||||
setAppDataPath: (path: string) => ipcRenderer.invoke(IpcChannel.App_SetAppDataPath, path),
|
||||
getDataPathFromArgs: () => ipcRenderer.invoke(IpcChannel.App_GetDataPathFromArgs),
|
||||
copy: (oldPath: string, newPath: string) => ipcRenderer.invoke(IpcChannel.App_Copy, oldPath, newPath),
|
||||
setStopQuitApp: (stop: boolean, reason: string) => ipcRenderer.invoke(IpcChannel.App_SetStopQuitApp, stop, reason),
|
||||
relaunchApp: () => ipcRenderer.invoke(IpcChannel.App_RelaunchApp),
|
||||
flushAppData: () => ipcRenderer.invoke(IpcChannel.App_FlushAppData),
|
||||
isNotEmptyDir: (path: string) => ipcRenderer.invoke(IpcChannel.App_IsNotEmptyDir, path),
|
||||
relaunchApp: (options?: Electron.RelaunchOptions) => ipcRenderer.invoke(IpcChannel.App_RelaunchApp, options),
|
||||
openWebsite: (url: string) => ipcRenderer.invoke(IpcChannel.Open_Website, url),
|
||||
getCacheSize: () => ipcRenderer.invoke(IpcChannel.App_GetCacheSize),
|
||||
clearCache: () => ipcRenderer.invoke(IpcChannel.App_ClearCache),
|
||||
|
||||
@ -30,6 +30,14 @@ export function useAppInit() {
|
||||
console.timeEnd('init')
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
window.api.getDataPathFromArgs().then((dataPath) => {
|
||||
if (dataPath) {
|
||||
window.navigate('/settings/data', { replace: true })
|
||||
}
|
||||
})
|
||||
}, [])
|
||||
|
||||
useUpdateHandler()
|
||||
useFullScreenNotice()
|
||||
|
||||
|
||||
@ -1087,10 +1087,10 @@
|
||||
"app_data": "App Data",
|
||||
"app_data.select": "Modify Directory",
|
||||
"app_data.select_title": "Change App Data Directory",
|
||||
"app_data.restart_notice": "The app will need to restart to apply the changes",
|
||||
"app_data.copy_data_option": "Copy data from original directory to new directory",
|
||||
"app_data.restart_notice": "The app may need to restart multiple times to apply the changes",
|
||||
"app_data.copy_data_option": "Copy data, will automatically restart after copying the original directory data to the new directory",
|
||||
"app_data.copy_time_notice": "Copying data may take a while, do not force quit app",
|
||||
"app_data.path_changed_without_copy": "Path changed successfully, but data not copied",
|
||||
"app_data.path_changed_without_copy": "Path changed successfully",
|
||||
"app_data.copying_warning": "Data copying, do not force quit app",
|
||||
"app_data.copying": "Copying data to new location...",
|
||||
"app_data.copy_success": "Successfully copied data to new location",
|
||||
@ -1103,6 +1103,9 @@
|
||||
"app_data.select_error_root_path": "New path cannot be the root path",
|
||||
"app_data.select_error_write_permission": "New path does not have write permission",
|
||||
"app_data.stop_quit_app_reason": "The app is currently migrating data and cannot be exited",
|
||||
"app_data.select_not_empty_dir": "New path is not empty",
|
||||
"app_data.select_not_empty_dir_content": "New path is not empty, if you select copy, it will overwrite the data in the new path, there is a risk of data loss, continue?",
|
||||
"app_data.select_error_same_path": "New path is the same as the old path, please select another path",
|
||||
"app_knowledge": "Knowledge Base Files",
|
||||
"app_knowledge.button.delete": "Delete File",
|
||||
"app_knowledge.remove_all": "Remove Knowledge Base Files",
|
||||
|
||||
@ -1085,10 +1085,10 @@
|
||||
"app_data": "アプリデータ",
|
||||
"app_data.select": "ディレクトリを変更",
|
||||
"app_data.select_title": "アプリデータディレクトリの変更",
|
||||
"app_data.restart_notice": "変更を適用するには、アプリを再起動する必要があります",
|
||||
"app_data.copy_data_option": "データをコピーする, 開くと元のディレクトリのデータが新しいディレクトリにコピーされます",
|
||||
"app_data.copy_time_notice": "データコピーには時間がかかります。アプリを強制終了しないでください",
|
||||
"app_data.path_changed_without_copy": "パスが変更されましたが、データがコピーされていません",
|
||||
"app_data.restart_notice": "変更を適用するには、アプリを再起動する必要があります。",
|
||||
"app_data.copy_data_option": "データをコピーする, 開くと元のディレクトリのデータが新しいディレクトリにコピーされます。",
|
||||
"app_data.copy_time_notice": "データコピーには時間がかかります。アプリを強制終了しないでください。",
|
||||
"app_data.path_changed_without_copy": "パスが変更されました。",
|
||||
"app_data.copying_warning": "データコピー中、アプリを強制終了しないでください",
|
||||
"app_data.copying": "新しい場所にデータをコピーしています...",
|
||||
"app_data.copy_success": "データを新しい場所に正常にコピーしました",
|
||||
@ -1101,6 +1101,9 @@
|
||||
"app_data.select_error_root_path": "新しいパスはルートパスにできません",
|
||||
"app_data.select_error_write_permission": "新しいパスに書き込み権限がありません",
|
||||
"app_data.stop_quit_app_reason": "アプリは現在データを移行しているため、終了できません",
|
||||
"app_data.select_not_empty_dir": "新しいパスは空ではありません",
|
||||
"app_data.select_not_empty_dir_content": "新しいパスは空ではありません。コピーを選択すると、新しいパスのデータが上書きされます。データが失われるリスクがあります。続行しますか?",
|
||||
"app_data.select_error_same_path": "新しいパスは元のパスと同じです。別のパスを選択してください",
|
||||
"app_knowledge": "知識ベースファイル",
|
||||
"app_knowledge.button.delete": "ファイルを削除",
|
||||
"app_knowledge.remove_all": "ナレッジベースファイルを削除",
|
||||
|
||||
@ -1085,10 +1085,10 @@
|
||||
"app_data": "Данные приложения",
|
||||
"app_data.select": "Изменить директорию",
|
||||
"app_data.select_title": "Изменить директорию данных приложения",
|
||||
"app_data.restart_notice": "Для применения изменений потребуется перезапуск приложения",
|
||||
"app_data.copy_data_option": "Копировать данные из исходной директории в новую директорию",
|
||||
"app_data.restart_notice": "Для применения изменений может потребоваться несколько перезапусков приложения",
|
||||
"app_data.copy_data_option": "Копировать данные, будет автоматически перезапущено после копирования данных из исходной директории в новую директорию",
|
||||
"app_data.copy_time_notice": "Копирование данных из исходной директории займет некоторое время, пожалуйста, будьте терпеливы",
|
||||
"app_data.path_changed_without_copy": "Путь изменен успешно, но данные не скопированы",
|
||||
"app_data.path_changed_without_copy": "Путь изменен успешно",
|
||||
"app_data.copying_warning": "Копирование данных, нельзя взаимодействовать с приложением, не закрывайте приложение",
|
||||
"app_data.copying": "Копирование данных в новое место...",
|
||||
"app_data.copy_success": "Данные успешно скопированы в новое место",
|
||||
@ -1101,6 +1101,9 @@
|
||||
"app_data.select_error_root_path": "Новый путь не может быть корневым",
|
||||
"app_data.select_error_write_permission": "Новый путь не имеет разрешения на запись",
|
||||
"app_data.stop_quit_app_reason": "Приложение в настоящее время перемещает данные и не может быть закрыто",
|
||||
"app_data.select_not_empty_dir": "Новый путь не пуст",
|
||||
"app_data.select_not_empty_dir_content": "Новый путь не пуст, если вы выбираете копирование, он перезапишет данные в новом пути, есть риск потери данных, продолжить?",
|
||||
"app_data.select_error_same_path": "Новый путь совпадает с исходным путем, пожалуйста, выберите другой путь",
|
||||
"app_knowledge": "Файлы базы знаний",
|
||||
"app_knowledge.button.delete": "Удалить файл",
|
||||
"app_knowledge.remove_all": "Удалить файлы базы знаний",
|
||||
|
||||
@ -1087,10 +1087,10 @@
|
||||
"app_data": "应用数据",
|
||||
"app_data.select": "修改目录",
|
||||
"app_data.select_title": "更改应用数据目录",
|
||||
"app_data.restart_notice": "应用需要重启以应用更改",
|
||||
"app_data.copy_data_option": "复制数据,开启后会将原始目录数据复制到新目录",
|
||||
"app_data.restart_notice": "应用可能会重启多次以应用更改",
|
||||
"app_data.copy_data_option": "复制数据,会自动重启后将原始目录数据复制到新目录",
|
||||
"app_data.copy_time_notice": "复制数据将需要一些时间,复制期间不要关闭应用",
|
||||
"app_data.path_changed_without_copy": "路径已更改成功,但数据未复制",
|
||||
"app_data.path_changed_without_copy": "路径已更改成功",
|
||||
"app_data.copying_warning": "数据复制中,不要强制退出app",
|
||||
"app_data.copying": "正在将数据复制到新位置...",
|
||||
"app_data.copy_success": "已成功复制数据到新位置",
|
||||
@ -1103,6 +1103,9 @@
|
||||
"app_data.select_error_root_path": "新路径不能是根路径",
|
||||
"app_data.select_error_write_permission": "新路径没有写入权限",
|
||||
"app_data.stop_quit_app_reason": "应用目前在迁移数据, 不能退出",
|
||||
"app_data.select_not_empty_dir": "新路径不为空",
|
||||
"app_data.select_not_empty_dir_content": "新路径不为空,选择复制将覆盖新路径中的数据, 有数据丢失的风险,是否继续?",
|
||||
"app_data.select_error_same_path": "新路径与旧路径相同,请选择其他路径",
|
||||
"app_knowledge": "知识库文件",
|
||||
"app_knowledge.button.delete": "删除文件",
|
||||
"app_knowledge.remove_all": "删除知识库文件",
|
||||
|
||||
@ -1087,10 +1087,10 @@
|
||||
"app_data": "應用數據",
|
||||
"app_data.select": "修改目錄",
|
||||
"app_data.select_title": "變更應用數據目錄",
|
||||
"app_data.restart_notice": "變更數據目錄後需要重啟應用才能生效",
|
||||
"app_data.copy_data_option": "複製數據, 開啟後會將原始目錄數據複製到新目錄",
|
||||
"app_data.restart_notice": "變更數據目錄後可能需要重啟應用才能生效",
|
||||
"app_data.copy_data_option": "複製數據, 會自動重啟後將原始目錄數據複製到新目錄",
|
||||
"app_data.copy_time_notice": "複製數據將需要一些時間,複製期間不要關閉應用",
|
||||
"app_data.path_changed_without_copy": "路徑已變更成功,但數據未複製",
|
||||
"app_data.path_changed_without_copy": "路徑已變更成功",
|
||||
"app_data.copying_warning": "數據複製中,不要強制退出應用",
|
||||
"app_data.copying": "正在複製數據到新位置...",
|
||||
"app_data.copy_success": "成功複製數據到新位置",
|
||||
@ -1103,6 +1103,9 @@
|
||||
"app_data.select_error_root_path": "新路徑不能是根路徑",
|
||||
"app_data.select_error_write_permission": "新路徑沒有寫入權限",
|
||||
"app_data.stop_quit_app_reason": "應用目前正在遷移數據,不能退出",
|
||||
"app_data.select_not_empty_dir": "新路徑不為空",
|
||||
"app_data.select_not_empty_dir_content": "新路徑不為空,選擇複製將覆蓋新路徑中的數據, 有數據丟失的風險,是否繼續?",
|
||||
"app_data.select_error_same_path": "新路徑與舊路徑相同,請選擇其他路徑",
|
||||
"app_knowledge": "知識庫文件",
|
||||
"app_knowledge.button.delete": "刪除檔案",
|
||||
"app_knowledge.remove_all": "刪除知識庫檔案",
|
||||
|
||||
@ -202,6 +202,12 @@ const DataSettings: FC = () => {
|
||||
return
|
||||
}
|
||||
|
||||
// check new app data path is same as old app data path
|
||||
if (newAppDataPath.startsWith(appInfo!.appDataPath)) {
|
||||
window.message.error(t('settings.data.app_data.select_error_same_path'))
|
||||
return
|
||||
}
|
||||
|
||||
// check new app data path has write permission
|
||||
const hasWritePermission = await window.api.hasWritePermission(newAppDataPath)
|
||||
if (!hasWritePermission) {
|
||||
@ -213,22 +219,34 @@ const DataSettings: FC = () => {
|
||||
<div style={{ fontSize: '18px', fontWeight: 'bold' }}>{t('settings.data.app_data.migration_title')}</div>
|
||||
)
|
||||
const migrationClassName = 'migration-modal'
|
||||
const messageKey = 'data-migration'
|
||||
|
||||
// 显示确认对话框
|
||||
showMigrationConfirmModal(appInfo.appDataPath, newAppDataPath, migrationTitle, migrationClassName, messageKey)
|
||||
if (await window.api.isNotEmptyDir(newAppDataPath)) {
|
||||
const modal = window.modal.confirm({
|
||||
title: t('settings.data.app_data.select_not_empty_dir'),
|
||||
content: t('settings.data.app_data.select_not_empty_dir_content'),
|
||||
centered: true,
|
||||
okText: t('common.confirm'),
|
||||
cancelText: t('common.cancel'),
|
||||
onOk: () => {
|
||||
modal.destroy()
|
||||
// 显示确认对话框
|
||||
showMigrationConfirmModal(appInfo.appDataPath, newAppDataPath, migrationTitle, migrationClassName)
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
showMigrationConfirmModal(appInfo.appDataPath, newAppDataPath, migrationTitle, migrationClassName)
|
||||
}
|
||||
|
||||
// 显示确认迁移的对话框
|
||||
const showMigrationConfirmModal = (
|
||||
const showMigrationConfirmModal = async (
|
||||
originalPath: string,
|
||||
newPath: string,
|
||||
title: React.ReactNode,
|
||||
className: string,
|
||||
messageKey: string
|
||||
className: string
|
||||
) => {
|
||||
// 复制数据选项状态
|
||||
let shouldCopyData = true
|
||||
let shouldCopyData = !(await window.api.isNotEmptyDir(newPath))
|
||||
|
||||
// 创建路径内容组件
|
||||
const PathsContent = () => (
|
||||
@ -248,7 +266,7 @@ const DataSettings: FC = () => {
|
||||
<div>
|
||||
<MigrationPathRow style={{ marginTop: '20px', flexDirection: 'row', alignItems: 'center' }}>
|
||||
<Switch
|
||||
defaultChecked={true}
|
||||
defaultChecked={shouldCopyData}
|
||||
onChange={(checked) => {
|
||||
shouldCopyData = checked
|
||||
}}
|
||||
@ -290,22 +308,17 @@ const DataSettings: FC = () => {
|
||||
// 立即关闭确认对话框
|
||||
modal.destroy()
|
||||
|
||||
// 设置停止退出应用
|
||||
window.api.setStopQuitApp(true, t('settings.data.app_data.stop_quit_app_reason'))
|
||||
|
||||
if (shouldCopyData) {
|
||||
// 如果选择复制数据,显示进度模态框并执行迁移
|
||||
const { loadingModal, progressInterval, updateProgress } = showProgressModal(title, className, PathsContent)
|
||||
|
||||
try {
|
||||
await startMigration(originalPath, newPath, progressInterval, updateProgress, loadingModal, messageKey)
|
||||
} catch (error) {
|
||||
if (progressInterval) {
|
||||
clearInterval(progressInterval)
|
||||
}
|
||||
loadingModal.destroy()
|
||||
throw error
|
||||
}
|
||||
window.message.info({
|
||||
content: t('settings.data.app_data.restart_notice'),
|
||||
duration: 3
|
||||
})
|
||||
setTimeout(() => {
|
||||
window.api.relaunchApp({
|
||||
args: ['--new-data-path=' + newPath]
|
||||
})
|
||||
}, 300)
|
||||
} else {
|
||||
// 如果不复制数据,直接设置新的应用数据路径
|
||||
await window.api.setAppDataPath(newPath)
|
||||
@ -324,12 +337,7 @@ const DataSettings: FC = () => {
|
||||
} catch (error) {
|
||||
window.api.setStopQuitApp(false, '')
|
||||
window.message.error({
|
||||
content:
|
||||
(shouldCopyData
|
||||
? t('settings.data.app_data.copy_failed')
|
||||
: t('settings.data.app_data.path_change_failed')) +
|
||||
': ' +
|
||||
error,
|
||||
content: t('settings.data.app_data.path_change_failed') + ': ' + error,
|
||||
duration: 5
|
||||
})
|
||||
}
|
||||
@ -337,6 +345,68 @@ const DataSettings: FC = () => {
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const handleDataMigration = async () => {
|
||||
const newDataPath = await window.api.getDataPathFromArgs()
|
||||
if (!newDataPath) return
|
||||
|
||||
const originalPath = (await window.api.getAppInfo())?.appDataPath
|
||||
if (!originalPath) return
|
||||
|
||||
const title = (
|
||||
<div style={{ fontSize: '18px', fontWeight: 'bold' }}>{t('settings.data.app_data.migration_title')}</div>
|
||||
)
|
||||
const className = 'migration-modal'
|
||||
const messageKey = 'data-migration'
|
||||
|
||||
// Create PathsContent component for this specific migration
|
||||
const PathsContent = () => (
|
||||
<div>
|
||||
<MigrationPathRow>
|
||||
<MigrationPathLabel>{t('settings.data.app_data.original_path')}:</MigrationPathLabel>
|
||||
<MigrationPathValue>{originalPath}</MigrationPathValue>
|
||||
</MigrationPathRow>
|
||||
<MigrationPathRow style={{ marginTop: '16px' }}>
|
||||
<MigrationPathLabel>{t('settings.data.app_data.new_path')}:</MigrationPathLabel>
|
||||
<MigrationPathValue>{newDataPath}</MigrationPathValue>
|
||||
</MigrationPathRow>
|
||||
</div>
|
||||
)
|
||||
|
||||
const { loadingModal, progressInterval, updateProgress } = showProgressModal(title, className, PathsContent)
|
||||
try {
|
||||
window.api.setStopQuitApp(true, t('settings.data.app_data.stop_quit_app_reason'))
|
||||
await startMigration(originalPath, newDataPath, progressInterval, updateProgress, loadingModal, messageKey)
|
||||
|
||||
// 更新应用数据路径
|
||||
setAppInfo(await window.api.getAppInfo())
|
||||
|
||||
// 通知用户并重启应用
|
||||
setTimeout(() => {
|
||||
window.message.success(t('settings.data.app_data.select_success'))
|
||||
window.api.setStopQuitApp(false, '')
|
||||
window.api.relaunchApp({
|
||||
args: ['--user-data-dir=' + newDataPath]
|
||||
})
|
||||
}, 1000)
|
||||
} catch (error) {
|
||||
window.api.setStopQuitApp(false, '')
|
||||
window.message.error({
|
||||
content: t('settings.data.app_data.copy_failed') + ': ' + error,
|
||||
key: messageKey,
|
||||
duration: 5
|
||||
})
|
||||
} finally {
|
||||
if (progressInterval) {
|
||||
clearInterval(progressInterval)
|
||||
}
|
||||
loadingModal.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
handleDataMigration()
|
||||
}, [])
|
||||
|
||||
// 显示进度模态框
|
||||
const showProgressModal = (title: React.ReactNode, className: string, PathsContent: React.FC) => {
|
||||
let currentProgress = 0
|
||||
@ -411,6 +481,9 @@ const DataSettings: FC = () => {
|
||||
loadingModal: { destroy: () => void },
|
||||
messageKey: string
|
||||
): Promise<void> => {
|
||||
// flush app data
|
||||
await window.api.flushAppData()
|
||||
|
||||
// 开始复制过程
|
||||
const copyResult = await window.api.copy(originalPath, newPath)
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user