diff --git a/src/main/bootstrap.ts b/src/main/bootstrap.ts index 15648f6ff..c4109c59e 100644 --- a/src/main/bootstrap.ts +++ b/src/main/bootstrap.ts @@ -3,7 +3,7 @@ import { app } from 'electron' import fs from 'fs' import path from 'path' -import { initAppDataDir } from './utils/file' +import { initAppDataDir } from './utils/init' app.isPackaged && initAppDataDir() diff --git a/src/main/ipc.ts b/src/main/ipc.ts index da096f131..afc25dd23 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -55,7 +55,8 @@ 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, updateAppDataConfig } from './utils/file' +import { getCacheDir, getConfigDir, getFilesDir, hasWritePermission } from './utils/file' +import { updateAppDataConfig } from './utils/init' import { compress, decompress } from './utils/zip' const logger = loggerService.withContext('IPC') diff --git a/src/main/utils/file.ts b/src/main/utils/file.ts index 19d37b28f..b6a040877 100644 --- a/src/main/utils/file.ts +++ b/src/main/utils/file.ts @@ -4,7 +4,6 @@ import os from 'node:os' import path from 'node:path' import { loggerService } from '@logger' -import { isLinux, isPortable, isWin } from '@main/constant' import { audioExts, documentExts, imageExts, MB, textExts, videoExts } from '@shared/config/constant' import { FileMetadata, FileTypes } from '@types' import { app } from 'electron' @@ -14,20 +13,6 @@ import { v4 as uuidv4 } from 'uuid' const logger = loggerService.withContext('Utils:File') -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() @@ -52,94 +37,6 @@ export function hasWritePermission(path: string) { } } -function getAppDataPathFromConfig() { - try { - const configPath = path.join(getConfigDir(), 'config.json') - if (!fs.existsSync(configPath)) { - return null - } - - const config = JSON.parse(fs.readFileSync(configPath, 'utf-8')) - - if (!config.appDataPath) { - return null - } - - let executablePath = app.getPath('exe') - if (isLinux && process.env.APPIMAGE) { - // 如果是 AppImage 打包的应用,直接使用 APPIMAGE 环境变量 - // 这样可以确保获取到正确的可执行文件路径 - executablePath = path.join(path.dirname(process.env.APPIMAGE), 'cherry-studio.appimage') - } - - if (isWin && isPortable) { - executablePath = path.join(process.env.PORTABLE_EXECUTABLE_DIR || '', 'cherry-studio-portable.exe') - } - - 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 === executablePath - )?.dataPath - } - - if (appDataPath && fs.existsSync(appDataPath) && hasWritePermission(appDataPath)) { - return appDataPath - } - - return null - } catch (error) { - return null - } -} - -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') - let executablePath = app.getPath('exe') - if (isLinux && process.env.APPIMAGE) { - executablePath = path.join(path.dirname(process.env.APPIMAGE), 'cherry-studio.appimage') - } - - // 如果是 Windows 可移植版本,则使用 PORTABLE_EXECUTABLE_FILE 环境变量 - if (isWin && isPortable) { - executablePath = path.join(process.env.PORTABLE_EXECUTABLE_DIR || '', 'cherry-studio-portable.exe') - } - - if (!fs.existsSync(configPath)) { - fs.writeFileSync(configPath, JSON.stringify({ appDataPath: [{ executablePath, dataPath: appDataPath }] }, null, 2)) - return - } - - const config = JSON.parse(fs.readFileSync(configPath, 'utf-8')) - if (!config.appDataPath || (config.appDataPath && typeof config.appDataPath !== 'object')) { - config.appDataPath = [] - } - - const existingPath = config.appDataPath.find( - (item: { executablePath: string }) => item.executablePath === executablePath - ) - - if (existingPath) { - existingPath.dataPath = appDataPath - } else { - config.appDataPath.push({ executablePath, dataPath: appDataPath }) - } - - fs.writeFileSync(configPath, JSON.stringify(config, null, 2)) -} - export function getFileType(ext: string): FileTypes { ext = ext.toLowerCase() return fileTypeMap.get(ext) || FileTypes.OTHER diff --git a/src/main/utils/init.ts b/src/main/utils/init.ts new file mode 100644 index 000000000..63cf69e89 --- /dev/null +++ b/src/main/utils/init.ts @@ -0,0 +1,123 @@ +import * as fs from 'node:fs' +import os from 'node:os' +import path from 'node:path' + +import { isLinux, isPortable, isWin } from '@main/constant' +import { app } from 'electron' + +// Please don't import any other modules which is not node/electron built-in modules + +function hasWritePermission(path: string) { + try { + fs.accessSync(path, fs.constants.W_OK) + return true + } catch (error) { + return false + } +} + +function getConfigDir() { + return path.join(os.homedir(), '.cherrystudio', 'config') +} + +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 + } +} + +function getAppDataPathFromConfig() { + try { + const configPath = path.join(getConfigDir(), 'config.json') + if (!fs.existsSync(configPath)) { + return null + } + + const config = JSON.parse(fs.readFileSync(configPath, 'utf-8')) + + if (!config.appDataPath) { + return null + } + + let executablePath = app.getPath('exe') + if (isLinux && process.env.APPIMAGE) { + // 如果是 AppImage 打包的应用,直接使用 APPIMAGE 环境变量 + // 这样可以确保获取到正确的可执行文件路径 + executablePath = path.join(path.dirname(process.env.APPIMAGE), 'cherry-studio.appimage') + } + + if (isWin && isPortable) { + executablePath = path.join(process.env.PORTABLE_EXECUTABLE_DIR || '', 'cherry-studio-portable.exe') + } + + 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 === executablePath + )?.dataPath + } + + if (appDataPath && fs.existsSync(appDataPath) && hasWritePermission(appDataPath)) { + return appDataPath + } + + return null + } catch (error) { + return null + } +} + +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(configDir, 'config.json') + let executablePath = app.getPath('exe') + if (isLinux && process.env.APPIMAGE) { + executablePath = path.join(path.dirname(process.env.APPIMAGE), 'cherry-studio.appimage') + } + + // 如果是 Windows 可移植版本,则使用 PORTABLE_EXECUTABLE_FILE 环境变量 + if (isWin && isPortable) { + executablePath = path.join(process.env.PORTABLE_EXECUTABLE_DIR || '', 'cherry-studio-portable.exe') + } + + if (!fs.existsSync(configPath)) { + fs.writeFileSync(configPath, JSON.stringify({ appDataPath: [{ executablePath, dataPath: appDataPath }] }, null, 2)) + return + } + + const config = JSON.parse(fs.readFileSync(configPath, 'utf-8')) + if (!config.appDataPath || (config.appDataPath && typeof config.appDataPath !== 'object')) { + config.appDataPath = [] + } + + const existingPath = config.appDataPath.find( + (item: { executablePath: string }) => item.executablePath === executablePath + ) + + if (existingPath) { + existingPath.dataPath = appDataPath + } else { + config.appDataPath.push({ executablePath, dataPath: appDataPath }) + } + + fs.writeFileSync(configPath, JSON.stringify(config, null, 2)) +}