From 160537bd48651c1e84ea5f129e3d7ec30d66b54e Mon Sep 17 00:00:00 2001 From: pompurin404 Date: Thu, 15 Aug 2024 15:42:02 +0800 Subject: [PATCH] support change data dir --- src/main/config/controledMihomo.ts | 2 +- src/main/{resolve => core}/factory.ts | 9 ++++ src/main/core/manager.ts | 2 +- src/main/core/mihomoApi.ts | 2 +- src/main/index.ts | 14 +++--- src/main/{core => resolve}/tray.ts | 6 +-- src/main/{resolve => sys}/autoRun.ts | 2 +- src/main/sys/misc.ts | 43 +++++++++++++++++++ src/main/{resolve => sys}/sysproxy.ts | 2 +- src/main/utils/dirs.ts | 47 +++++++++++++++----- src/main/{resolve => utils}/init.ts | 12 +++--- src/main/utils/ipc.ts | 62 ++++----------------------- src/renderer/src/pages/settings.tsx | 35 ++++++++++++--- src/renderer/src/utils/ipc.ts | 8 ++++ 14 files changed, 154 insertions(+), 92 deletions(-) rename src/main/{resolve => core}/factory.ts (87%) rename src/main/{core => resolve}/tray.ts (97%) rename src/main/{resolve => sys}/autoRun.ts (98%) create mode 100644 src/main/sys/misc.ts rename src/main/{resolve => sys}/sysproxy.ts (97%) rename src/main/{resolve => utils}/init.ts (94%) diff --git a/src/main/config/controledMihomo.ts b/src/main/config/controledMihomo.ts index e46bc14..c26c516 100644 --- a/src/main/config/controledMihomo.ts +++ b/src/main/config/controledMihomo.ts @@ -2,7 +2,7 @@ import { controledMihomoConfigPath } from '../utils/dirs' import { readFile, writeFile } from 'fs/promises' import yaml from 'yaml' import { getAxios, startMihomoMemory, startMihomoTraffic } from '../core/mihomoApi' -import { generateProfile } from '../resolve/factory' +import { generateProfile } from '../core/factory' import { getAppConfig } from './app' import { defaultControledMihomoConfig } from '../utils/template' diff --git a/src/main/resolve/factory.ts b/src/main/core/factory.ts similarity index 87% rename from src/main/resolve/factory.ts rename to src/main/core/factory.ts index 2f74f20..8c3e79a 100644 --- a/src/main/resolve/factory.ts +++ b/src/main/core/factory.ts @@ -8,6 +8,7 @@ import { import { mihomoWorkConfigPath } from '../utils/dirs' import yaml from 'yaml' import fs from 'fs' +import { readFile } from 'fs/promises' export async function generateProfile(): Promise { const { current } = await getProfileConfig() @@ -59,3 +60,11 @@ function runOverrideScript(profile: IMihomoConfig, script: string): IMihomoConfi return profile } } + +export async function getRuntimeConfigStr(): Promise { + return await readFile(mihomoWorkConfigPath(), 'utf8') +} + +export async function getRuntimeConfig(): Promise { + return yaml.parse(await getRuntimeConfigStr()) +} diff --git a/src/main/core/manager.ts b/src/main/core/manager.ts index 4e2c928..3d95016 100644 --- a/src/main/core/manager.ts +++ b/src/main/core/manager.ts @@ -6,7 +6,7 @@ import { mihomoWorkConfigPath, mihomoWorkDir } from '../utils/dirs' -import { generateProfile } from '../resolve/factory' +import { generateProfile } from './factory' import { getAppConfig, patchAppConfig } from '../config' import { dialog, safeStorage } from 'electron' import { pauseWebsockets } from './mihomoApi' diff --git a/src/main/core/mihomoApi.ts b/src/main/core/mihomoApi.ts index 29ea7ff..1d05fe2 100644 --- a/src/main/core/mihomoApi.ts +++ b/src/main/core/mihomoApi.ts @@ -2,7 +2,7 @@ import axios, { AxiosInstance } from 'axios' import { getAppConfig, getControledMihomoConfig } from '../config' import { mainWindow } from '..' import WebSocket from 'ws' -import { tray } from './tray' +import { tray } from '../resolve/tray' import { calcTraffic } from '../utils/calc' let axiosIns: AxiosInstance = null! diff --git a/src/main/index.ts b/src/main/index.ts index 41c1ff3..b4c08cc 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -2,14 +2,14 @@ import { electronApp, optimizer, is } from '@electron-toolkit/utils' import { registerIpcMainHandlers } from './utils/ipc' import windowStateKeeper from 'electron-window-state' import { app, shell, BrowserWindow, Menu, dialog, Notification } from 'electron' -import { stopCore } from './core/manager' -import { triggerSysProxy } from './resolve/sysproxy' -import icon from '../../resources/icon.png?asset' -import { createTray } from './core/tray' -import { init } from './resolve/init' -import { addProfileItem, getAppConfig } from './config' -import { join } from 'path' import { startMihomoMemory, stopMihomoMemory } from './core/mihomoApi' +import { addProfileItem, getAppConfig } from './config' +import { stopCore } from './core/manager' +import { triggerSysProxy } from './sys/sysproxy' +import icon from '../../resources/icon.png?asset' +import { createTray } from './resolve/tray' +import { init } from './utils/init' +import { join } from 'path' export let mainWindow: BrowserWindow | null = null export let destroyTimer: NodeJS.Timeout | null = null diff --git a/src/main/core/tray.ts b/src/main/resolve/tray.ts similarity index 97% rename from src/main/core/tray.ts rename to src/main/resolve/tray.ts index 9f79f35..8a50232 100644 --- a/src/main/core/tray.ts +++ b/src/main/resolve/tray.ts @@ -7,11 +7,11 @@ import { import icoIcon from '../../../resources/icon.ico?asset' import pngIcon from '../../../resources/icon.png?asset' import templateIcon from '../../../resources/iconTemplate.png?asset' -import { patchMihomoConfig } from './mihomoApi' +import { patchMihomoConfig } from '../core/mihomoApi' import { mainWindow, showMainWindow } from '..' import { app, ipcMain, Menu, nativeImage, shell, Tray } from 'electron' import { dataDir, logDir, mihomoCoreDir, mihomoWorkDir } from '../utils/dirs' -import { triggerSysProxy } from '../resolve/sysproxy' +import { triggerSysProxy } from '../sys/sysproxy' export let tray: Tray | null = null @@ -105,7 +105,7 @@ const buildContextMenu = async (): Promise => { { type: 'normal', label: '应用目录', - click: (): Promise => shell.openPath(dataDir) + click: (): Promise => shell.openPath(dataDir()) }, { type: 'normal', diff --git a/src/main/resolve/autoRun.ts b/src/main/sys/autoRun.ts similarity index 98% rename from src/main/resolve/autoRun.ts rename to src/main/sys/autoRun.ts index 5874b74..922b36a 100644 --- a/src/main/resolve/autoRun.ts +++ b/src/main/sys/autoRun.ts @@ -76,7 +76,7 @@ export async function checkAutoRun(): Promise { export async function enableAutoRun(): Promise { if (process.platform === 'win32') { const execPromise = promisify(exec) - const taskFilePath = path.join(dataDir, `${appName}.xml`) + const taskFilePath = path.join(dataDir(), `${appName}.xml`) await writeFile(taskFilePath, taskXml) await execPromise(`schtasks /create /tn "${appName}" /xml "${taskFilePath}" /f`) } diff --git a/src/main/sys/misc.ts b/src/main/sys/misc.ts new file mode 100644 index 0000000..e4aaf47 --- /dev/null +++ b/src/main/sys/misc.ts @@ -0,0 +1,43 @@ +import { exec, execFile } from 'child_process' +import { dialog } from 'electron' +import { readFile } from 'fs/promises' +import path from 'path' +import { promisify } from 'util' +import { exePath, mihomoCorePath, resourcesDir } from '../utils/dirs' + +export function getFilePath(ext: string[]): string[] | undefined { + return dialog.showOpenDialogSync({ + title: '选择订阅文件', + filters: [{ name: `${ext} file`, extensions: ext }], + properties: ['openFile'] + }) +} + +export async function readTextFile(filePath: string): Promise { + return await readFile(filePath, 'utf8') +} + +export async function openUWPTool(): Promise { + const execFilePromise = promisify(execFile) + const uwpToolPath = path.join(resourcesDir(), 'files', 'enableLoopback.exe') + await execFilePromise(uwpToolPath) +} + +export async function setupFirewall(): Promise { + const execPromise = promisify(exec) + const removeCommand = ` + Remove-NetFirewallRule -DisplayName "mihomo" -ErrorAction SilentlyContinue + Remove-NetFirewallRule -DisplayName "mihomo-alpha" -ErrorAction SilentlyContinue + Remove-NetFirewallRule -DisplayName "Mihomo Party" -ErrorAction SilentlyContinue + ` + const createCommand = ` + New-NetFirewallRule -DisplayName "mihomo" -Direction Inbound -Action Allow -Program "${mihomoCorePath('mihomo')}" -Enabled True -Profile Any -ErrorAction SilentlyContinue + New-NetFirewallRule -DisplayName "mihomo-alpha" -Direction Inbound -Action Allow -Program "${mihomoCorePath('mihomo-alpha')}" -Enabled True -Profile Any -ErrorAction SilentlyContinue + New-NetFirewallRule -DisplayName "Mihomo Party" -Direction Inbound -Action Allow -Program "${exePath()}" -Enabled True -Profile Any -ErrorAction SilentlyContinue + ` + + if (process.platform === 'win32') { + await execPromise(removeCommand, { shell: 'powershell' }) + await execPromise(createCommand, { shell: 'powershell' }) + } +} diff --git a/src/main/resolve/sysproxy.ts b/src/main/sys/sysproxy.ts similarity index 97% rename from src/main/resolve/sysproxy.ts rename to src/main/sys/sysproxy.ts index 3ba1de6..5fd9677 100644 --- a/src/main/resolve/sysproxy.ts +++ b/src/main/sys/sysproxy.ts @@ -1,6 +1,6 @@ import { triggerAutoProxy, triggerManualProxy } from '@mihomo-party/sysproxy' import { getAppConfig, getControledMihomoConfig } from '../config' -import { pacPort } from './server' +import { pacPort } from '../resolve/server' let defaultBypass: string[] diff --git a/src/main/utils/dirs.ts b/src/main/utils/dirs.ts index ea930cc..4cdb7e2 100644 --- a/src/main/utils/dirs.ts +++ b/src/main/utils/dirs.ts @@ -1,10 +1,37 @@ import { is } from '@electron-toolkit/utils' import { app } from 'electron' +import fs from 'fs' +import { rm, writeFile } from 'fs/promises' import path from 'path' -export const dataDir = app.getPath('userData') export const homeDir = app.getPath('home') +export function isPortable(): boolean { + return fs.existsSync(path.join(exeDir(), 'PORTABLE')) +} + +export async function setPortable(portable: boolean): Promise { + if (portable) { + await writeFile(path.join(exeDir(), 'PORTABLE'), '') + } else { + await rm(path.join(exeDir(), 'PORTABLE')) + } + app.relaunch() + app.quit() +} + +export function dataDir(): string { + if (isPortable()) { + return path.join(exeDir(), 'data') + } else { + return app.getPath('userData') + } +} + +export function exeDir(): string { + return path.dirname(exePath()) +} + export function exePath(): string { return app.getPath('exe') } @@ -35,19 +62,19 @@ export function mihomoCorePath(core: string): string { } export function appConfigPath(): string { - return path.join(dataDir, 'config.yaml') + return path.join(dataDir(), 'config.yaml') } export function controledMihomoConfigPath(): string { - return path.join(dataDir, 'mihomo.yaml') + return path.join(dataDir(), 'mihomo.yaml') } export function profileConfigPath(): string { - return path.join(dataDir, 'profile.yaml') + return path.join(dataDir(), 'profile.yaml') } export function profilesDir(): string { - return path.join(dataDir, 'profiles') + return path.join(dataDir(), 'profiles') } export function profilePath(id: string): string { @@ -55,11 +82,11 @@ export function profilePath(id: string): string { } export function overrideDir(): string { - return path.join(dataDir, 'override') + return path.join(dataDir(), 'override') } export function overrideConfigPath(): string { - return path.join(dataDir, 'override.yaml') + return path.join(dataDir(), 'override.yaml') } export function overridePath(id: string): string { @@ -67,11 +94,11 @@ export function overridePath(id: string): string { } export function mihomoWorkDir(): string { - return path.join(dataDir, 'work') + return path.join(dataDir(), 'work') } export function mihomoTestDir(): string { - return path.join(dataDir, 'test') + return path.join(dataDir(), 'test') } export function mihomoWorkConfigPath(): string { @@ -79,7 +106,7 @@ export function mihomoWorkConfigPath(): string { } export function logDir(): string { - return path.join(dataDir, 'logs') + return path.join(dataDir(), 'logs') } export function logPath(): string { diff --git a/src/main/resolve/init.ts b/src/main/utils/init.ts similarity index 94% rename from src/main/resolve/init.ts rename to src/main/utils/init.ts index 546606c..dbdd8b0 100644 --- a/src/main/resolve/init.ts +++ b/src/main/utils/init.ts @@ -11,20 +11,20 @@ import { profilePath, profilesDir, resourcesFilesDir -} from '../utils/dirs' +} from './dirs' import { defaultConfig, defaultControledMihomoConfig, defaultOverrideConfig, defaultProfile, defaultProfileConfig -} from '../utils/template' +} from './template' import yaml from 'yaml' import { mkdir, writeFile, copyFile } from 'fs/promises' import { existsSync } from 'fs' import path from 'path' -import { startPacServer } from './server' -import { triggerSysProxy } from './sysproxy' +import { startPacServer } from '../resolve/server' +import { triggerSysProxy } from '../sys/sysproxy' import { getAppConfig } from '../config' import { app } from 'electron' import { startCore } from '../core/manager' @@ -32,8 +32,8 @@ import { initProfileUpdater } from '../core/profileUpdater' import { startMihomoTraffic } from '../core/mihomoApi' async function initDirs(): Promise { - if (!existsSync(dataDir)) { - await mkdir(dataDir) + if (!existsSync(dataDir())) { + await mkdir(dataDir()) } if (!existsSync(profilesDir())) { await mkdir(profilesDir()) diff --git a/src/main/utils/ipc.ts b/src/main/utils/ipc.ts index dd14ebd..5499522 100644 --- a/src/main/utils/ipc.ts +++ b/src/main/utils/ipc.ts @@ -1,4 +1,4 @@ -import { app, dialog, ipcMain, safeStorage } from 'electron' +import { app, ipcMain, safeStorage } from 'electron' import { mihomoChangeProxy, mihomoCloseAllConnections, @@ -19,7 +19,7 @@ import { stopMihomoConnections, stopMihomoLogs } from '../core/mihomoApi' -import { checkAutoRun, disableAutoRun, enableAutoRun } from '../resolve/autoRun' +import { checkAutoRun, disableAutoRun, enableAutoRun } from '../sys/autoRun' import { getAppConfig, patchAppConfig, @@ -45,14 +45,11 @@ import { updateOverrideItem } from '../config' import { isEncryptionAvailable, manualGrantCorePermition, restartCore } from '../core/manager' -import { triggerSysProxy } from '../resolve/sysproxy' +import { triggerSysProxy } from '../sys/sysproxy' import { checkUpdate } from '../resolve/autoUpdater' -import { exePath, mihomoCorePath, mihomoWorkConfigPath, resourcesDir } from './dirs' -import { exec, execFile } from 'child_process' -import yaml from 'yaml' -import path from 'path' -import { promisify } from 'util' -import { readFile } from 'fs/promises' +import { getFilePath, openUWPTool, readTextFile, setupFirewall } from '../sys/misc' +import { getRuntimeConfig, getRuntimeConfigStr } from '../core/factory' +import { isPortable, setPortable } from './dirs' function ipcErrorWrapper( // eslint-disable-next-line @typescript-eslint/no-explicit-any fn: (...args: any[]) => Promise // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -138,50 +135,7 @@ export function registerIpcMainHandlers(): void { ipcMain.handle('platform', () => process.platform) ipcMain.handle('openUWPTool', ipcErrorWrapper(openUWPTool)) ipcMain.handle('setupFirewall', ipcErrorWrapper(setupFirewall)) + ipcMain.handle('setPortable', (_e, portable) => ipcErrorWrapper(setPortable)(portable)) + ipcMain.handle('isPortable', isPortable) ipcMain.handle('quitApp', () => app.quit()) } - -function getFilePath(ext: string[]): string[] | undefined { - return dialog.showOpenDialogSync({ - title: '选择订阅文件', - filters: [{ name: `${ext} file`, extensions: ext }], - properties: ['openFile'] - }) -} - -async function readTextFile(filePath: string): Promise { - return await readFile(filePath, 'utf8') -} - -async function getRuntimeConfigStr(): Promise { - return readFile(mihomoWorkConfigPath(), 'utf8') -} - -async function getRuntimeConfig(): Promise { - return yaml.parse(await getRuntimeConfigStr()) -} - -async function openUWPTool(): Promise { - const execFilePromise = promisify(execFile) - const uwpToolPath = path.join(resourcesDir(), 'files', 'enableLoopback.exe') - await execFilePromise(uwpToolPath) -} - -async function setupFirewall(): Promise { - const execPromise = promisify(exec) - const removeCommand = ` - Remove-NetFirewallRule -DisplayName "mihomo" -ErrorAction SilentlyContinue - Remove-NetFirewallRule -DisplayName "mihomo-alpha" -ErrorAction SilentlyContinue - Remove-NetFirewallRule -DisplayName "Mihomo Party" -ErrorAction SilentlyContinue - ` - const createCommand = ` - New-NetFirewallRule -DisplayName "mihomo" -Direction Inbound -Action Allow -Program "${mihomoCorePath('mihomo')}" -Enabled True -Profile Any -ErrorAction SilentlyContinue - New-NetFirewallRule -DisplayName "mihomo-alpha" -Direction Inbound -Action Allow -Program "${mihomoCorePath('mihomo-alpha')}" -Enabled True -Profile Any -ErrorAction SilentlyContinue - New-NetFirewallRule -DisplayName "Mihomo Party" -Direction Inbound -Action Allow -Program "${exePath()}" -Enabled True -Profile Any -ErrorAction SilentlyContinue - ` - - if (process.platform === 'win32') { - await execPromise(removeCommand, { shell: 'powershell' }) - await execPromise(createCommand, { shell: 'powershell' }) - } -} diff --git a/src/renderer/src/pages/settings.tsx b/src/renderer/src/pages/settings.tsx index b1bdea5..151de38 100644 --- a/src/renderer/src/pages/settings.tsx +++ b/src/renderer/src/pages/settings.tsx @@ -1,4 +1,4 @@ -import { Button, Input, Switch, Tab, Tabs } from '@nextui-org/react' +import { Button, Input, Select, SelectItem, Switch, Tab, Tabs } from '@nextui-org/react' import BasePage from '@renderer/components/base/base-page' import SettingCard from '@renderer/components/base/base-setting-card' import SettingItem from '@renderer/components/base/base-setting-item' @@ -9,7 +9,9 @@ import { disableAutoRun, quitApp, checkUpdate, - patchControledMihomoConfig + patchControledMihomoConfig, + isPortable, + setPortable } from '@renderer/utils/ipc' import { IoLogoGithub } from 'react-icons/io5' import { platform, version } from '@renderer/utils/init' @@ -20,10 +22,8 @@ import { useTheme } from 'next-themes' const Settings: React.FC = () => { const { setTheme } = useTheme() - const { data: enable, mutate } = useSWR('checkAutoRun', checkAutoRun, { - errorRetryCount: 5, - errorRetryInterval: 200 - }) + const { data: enable, mutate: mutateEnable } = useSWR('checkAutoRun', checkAutoRun) + const { data: portable, mutate: mutatePortable } = useSWR('isPortable', isPortable) const { appConfig, patchAppConfig } = useAppConfig() const { silentStart = false, @@ -100,7 +100,7 @@ const Settings: React.FC = () => { } catch (e) { alert(e) } finally { - mutate() + mutateEnable() } }} /> @@ -134,6 +134,27 @@ const Settings: React.FC = () => { /> )} + {platform === 'win32' && ( + + + + )} { return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('setupFirewall')) } +export async function setPortable(portable: boolean): Promise { + return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('setPortable', portable)) +} + +export async function isPortable(): Promise { + return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('isPortable')) +} + export async function quitApp(): Promise { return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('quitApp')) }