From f30596231b397925df3aa93c2904347198a8397a Mon Sep 17 00:00:00 2001 From: Alan Lin Date: Mon, 3 Mar 2025 09:09:09 +0800 Subject: [PATCH] fix: update sub-store on linux (#545) thanks --- src/main/resolve/server.ts | 66 ++++++++++++----- src/main/utils/ipc.ts | 5 +- src/renderer/src/pages/substore.tsx | 107 ++++++++++++++++++++-------- src/renderer/src/utils/ipc.ts | 4 +- 4 files changed, 129 insertions(+), 53 deletions(-) diff --git a/src/main/resolve/server.ts b/src/main/resolve/server.ts index de2d77c..e2e49d3 100644 --- a/src/main/resolve/server.ts +++ b/src/main/resolve/server.ts @@ -1,6 +1,12 @@ import { getAppConfig, getControledMihomoConfig } from '../config' import { Worker } from 'worker_threads' -import { mihomoWorkDir, resourcesFilesDir, subStoreDir, substoreLogPath } from '../utils/dirs' +import { + dataDir, + mihomoWorkDir, + resourcesFilesDir, + subStoreDir, + substoreLogPath +} from '../utils/dirs' import subStoreIcon from '../../../resources/subStoreIcon.png?asset' import { createWriteStream, existsSync, mkdirSync } from 'fs' import { writeFile, rm, cp } from 'fs/promises' @@ -11,6 +17,9 @@ import { nativeImage } from 'electron' import express from 'express' import axios from 'axios' import AdmZip from 'adm-zip' +import { promisify } from 'util' +import { exec } from 'child_process' +import { platform } from 'os' export let pacPort: number export let subStorePort: number @@ -139,14 +148,22 @@ export async function stopSubStoreBackendServer(): Promise { } } -export async function downloadSubStore(): Promise { +export async function downloadSubStore(password?: string): Promise { const { 'mixed-port': mixedPort = 7890 } = await getControledMihomoConfig() const frontendDir = path.join(resourcesFilesDir(), 'sub-store-frontend') const backendPath = path.join(resourcesFilesDir(), 'sub-store.bundle.js') - const tempDir = path.join(resourcesFilesDir(), 'temp') + const tempDir = path.join(dataDir(), 'temp') + const execPromise = promisify(exec) try { + // 创建临时目录 + if (existsSync(tempDir)) { + await rm(tempDir, { recursive: true }) + } + mkdirSync(tempDir, { recursive: true }) + // 下载后端文件 + const tempBackendPath = path.join(tempDir, 'sub-store.bundle.js') const backendRes = await axios.get( 'https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store.bundle.js', { @@ -159,9 +176,9 @@ export async function downloadSubStore(): Promise { } } ) - await writeFile(backendPath, Buffer.from(backendRes.data)) - + await writeFile(tempBackendPath, Buffer.from(backendRes.data)) // 下载前端文件 + const tempFrontendDir = path.join(tempDir, 'dist') const frontendRes = await axios.get( 'https://github.com/sub-store-org/Sub-Store-Front-End/releases/latest/download/dist.zip', { @@ -174,25 +191,36 @@ export async function downloadSubStore(): Promise { } } ) - - // 创建临时目录 - if (existsSync(tempDir)) { - await rm(tempDir, { recursive: true }) - } - mkdirSync(tempDir, { recursive: true }) - // 先解压到临时目录 const zip = new AdmZip(Buffer.from(frontendRes.data)) zip.extractAllTo(tempDir, true) - // 确保目标目录存在并清空 - if (existsSync(frontendDir)) { - await rm(frontendDir, { recursive: true }) + // 如果是 Linux 平台,使用 sudo cp 移动文件 + if (platform() === 'linux') { + try { + await execPromise(`echo "${password}" | sudo -S cp "${tempBackendPath}" "${backendPath}"`) + // 确保目标目录存在并清空 + if (existsSync(frontendDir)) { + await execPromise(`echo "${password}" | sudo -S rm -r "${frontendDir}"`) + } + await execPromise(`echo "${password}" | sudo -S mkdir "${frontendDir}"`) + // 将 dist 目录中的内容移动到目标目录 + await execPromise( + `echo "${password}" | sudo -S cp -r "${tempFrontendDir}"/* "${frontendDir}/"` + ) + } catch (error) { + console.error('substore.downloadFailed:', error) + throw error + } + } else { + // 非 Linux 平台 + await cp(tempBackendPath, backendPath) + if (existsSync(frontendDir)) { + await rm(frontendDir, { recursive: true }) + } + mkdirSync(frontendDir, { recursive: true }) + await cp(path.join(tempDir, 'dist'), frontendDir, { recursive: true }) } - mkdirSync(frontendDir, { recursive: true }) - - // 将 dist 目录中的内容移动到目标目录 - await cp(path.join(tempDir, 'dist'), frontendDir, { recursive: true }) // 清理临时目录 await rm(tempDir, { recursive: true }) diff --git a/src/main/utils/ipc.ts b/src/main/utils/ipc.ts index 00ca938..5321053 100644 --- a/src/main/utils/ipc.ts +++ b/src/main/utils/ipc.ts @@ -203,8 +203,7 @@ export function registerIpcMainHandlers(): void { ipcMain.handle('stopSubStoreFrontendServer', () => ipcErrorWrapper(stopSubStoreFrontendServer)()) ipcMain.handle('startSubStoreBackendServer', () => ipcErrorWrapper(startSubStoreBackendServer)()) ipcMain.handle('stopSubStoreBackendServer', () => ipcErrorWrapper(stopSubStoreBackendServer)()) - ipcMain.handle('downloadSubStore', () => ipcErrorWrapper(downloadSubStore)()) - + ipcMain.handle('downloadSubStore', (_e, password) => ipcErrorWrapper(downloadSubStore)(password)) ipcMain.handle('subStorePort', () => subStorePort) ipcMain.handle('subStoreFrontendPort', () => subStoreFrontendPort) ipcMain.handle('subStoreSubs', () => ipcErrorWrapper(subStoreSubs)()) @@ -259,7 +258,7 @@ export function registerIpcMainHandlers(): void { }) ipcMain.handle('quitWithoutCore', ipcErrorWrapper(quitWithoutCore)) ipcMain.handle('quitApp', () => app.quit()) - + // Add language change handler ipcMain.handle('changeLanguage', async (_e, lng) => { await i18next.changeLanguage(lng) diff --git a/src/renderer/src/pages/substore.tsx b/src/renderer/src/pages/substore.tsx index 2e1bdab..e5c6f61 100644 --- a/src/renderer/src/pages/substore.tsx +++ b/src/renderer/src/pages/substore.tsx @@ -14,6 +14,8 @@ import React, { useEffect, useState } from 'react' import { HiExternalLink } from 'react-icons/hi' import { useTranslation } from 'react-i18next' import { IoMdCloudDownload } from 'react-icons/io' +import BasePasswordModal from '@renderer/components/base/base-password-modal' +import { platform } from '@renderer/utils/init' const SubStore: React.FC = () => { const { t } = useTranslation() @@ -22,6 +24,7 @@ const SubStore: React.FC = () => { const [backendPort, setBackendPort] = useState() const [frontendPort, setFrontendPort] = useState() const [isUpdating, setIsUpdating] = useState(false) + const [openPasswordModal, setOpenPasswordModal] = useState(false) const getPort = async (): Promise => { setBackendPort(await subStorePort()) setFrontendPort(await subStoreFrontendPort()) @@ -34,39 +37,85 @@ const SubStore: React.FC = () => { if (!frontendPort) return null return ( <> + {openPasswordModal && ( + setOpenPasswordModal(false)} + onConfirm={async (password: string) => { + try { + setOpenPasswordModal(false) + new Notification(t('substore.updating')) + await downloadSubStore(password) + await stopSubStoreBackendServer() + await startSubStoreBackendServer() + await new Promise((resolve) => setTimeout(resolve, 1000)) + setFrontendPort(0) + await stopSubStoreFrontendServer() + await startSubStoreFrontendServer() + await getPort() + new Notification(t('substore.updateCompleted')) + } catch (e) { + alert(e) + } + }} + /> + )} - + {platform != 'linux' && ( + + )} + {platform === 'linux' && ( + + )}