From 4da4c242adbe670d86a696facd631859488177bc Mon Sep 17 00:00:00 2001 From: ezequielnick <107352853+ezequielnick@users.noreply.github.com> Date: Sat, 8 Feb 2025 05:12:18 +0800 Subject: [PATCH] feat: support sub-store update & restart --- src/main/resolve/server.ts | 68 ++++++++++++++++++++++++++- src/main/utils/ipc.ts | 2 + src/renderer/src/locales/en-US.json | 5 ++ src/renderer/src/locales/zh-CN.json | 5 ++ src/renderer/src/pages/substore.tsx | 71 +++++++++++++++++++++++------ src/renderer/src/utils/ipc.ts | 3 ++ 6 files changed, 138 insertions(+), 16 deletions(-) diff --git a/src/main/resolve/server.ts b/src/main/resolve/server.ts index 024d41a..de2d77c 100644 --- a/src/main/resolve/server.ts +++ b/src/main/resolve/server.ts @@ -2,12 +2,15 @@ import { getAppConfig, getControledMihomoConfig } from '../config' import { Worker } from 'worker_threads' import { mihomoWorkDir, resourcesFilesDir, subStoreDir, substoreLogPath } from '../utils/dirs' import subStoreIcon from '../../../resources/subStoreIcon.png?asset' -import { createWriteStream } from 'fs' +import { createWriteStream, existsSync, mkdirSync } from 'fs' +import { writeFile, rm, cp } from 'fs/promises' import http from 'http' import net from 'net' import path from 'path' import { nativeImage } from 'electron' import express from 'express' +import axios from 'axios' +import AdmZip from 'adm-zip' export let pacPort: number export let subStorePort: number @@ -135,3 +138,66 @@ export async function stopSubStoreBackendServer(): Promise { subStoreBackendWorker.terminate() } } + +export async function downloadSubStore(): 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') + + try { + // 下载后端文件 + const backendRes = await axios.get( + 'https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store.bundle.js', + { + responseType: 'arraybuffer', + headers: { 'Content-Type': 'application/octet-stream' }, + proxy: { + protocol: 'http', + host: '127.0.0.1', + port: mixedPort + } + } + ) + await writeFile(backendPath, Buffer.from(backendRes.data)) + + // 下载前端文件 + const frontendRes = await axios.get( + 'https://github.com/sub-store-org/Sub-Store-Front-End/releases/latest/download/dist.zip', + { + responseType: 'arraybuffer', + headers: { 'Content-Type': 'application/octet-stream' }, + proxy: { + protocol: 'http', + host: '127.0.0.1', + port: mixedPort + } + } + ) + + // 创建临时目录 + 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 }) + } + mkdirSync(frontendDir, { recursive: true }) + + // 将 dist 目录中的内容移动到目标目录 + await cp(path.join(tempDir, 'dist'), frontendDir, { recursive: true }) + + // 清理临时目录 + await rm(tempDir, { recursive: true }) + } catch (error) { + console.error('substore.downloadFailed:', error) + throw error + } +} diff --git a/src/main/utils/ipc.ts b/src/main/utils/ipc.ts index 31cb084..00ca938 100644 --- a/src/main/utils/ipc.ts +++ b/src/main/utils/ipc.ts @@ -50,6 +50,7 @@ import { startSubStoreBackendServer, stopSubStoreFrontendServer, stopSubStoreBackendServer, + downloadSubStore, subStoreFrontendPort, subStorePort } from '../resolve/server' @@ -202,6 +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('subStorePort', () => subStorePort) ipcMain.handle('subStoreFrontendPort', () => subStoreFrontendPort) diff --git a/src/renderer/src/locales/en-US.json b/src/renderer/src/locales/en-US.json index fa02c02..ea168f4 100644 --- a/src/renderer/src/locales/en-US.json +++ b/src/renderer/src/locales/en-US.json @@ -148,6 +148,11 @@ "mihomo.ipSegment.placeholder": "IP Segment", "mihomo.interface.title": "Network Information", "substore.title": "Sub-Store", + "substore.checkUpdate": "Check Update", + "substore.updating": "Sub-Store is updating...", + "substore.updateCompleted": "Sub-Store update completed", + "substore.updateFailed": "Sub-Store update failed", + "substore.downloadFailed": "Failed to download Sub-Store file", "substore.openInBrowser": "Open in Browser", "substore.enable": "Enable Sub-Store", "substore.allowLan": "Allow LAN Access", diff --git a/src/renderer/src/locales/zh-CN.json b/src/renderer/src/locales/zh-CN.json index d5cf5f3..4c6a9e1 100644 --- a/src/renderer/src/locales/zh-CN.json +++ b/src/renderer/src/locales/zh-CN.json @@ -148,6 +148,11 @@ "mihomo.ipSegment.placeholder": "IP 段", "mihomo.interface.title": "网络信息", "substore.title": "Sub-Store", + "substore.checkUpdate": "检查更新", + "substore.updating": "Sub-Store 更新中...", + "substore.updateCompleted": "Sub-Store 更新完成", + "substore.updateFailed": "Sub-Store 更新失败", + "substore.downloadFailed": "下载 Sub-Store 文件失败", "substore.openInBrowser": "在浏览器中打开", "substore.enable": "启用 Sub-Store", "substore.allowLan": "允许局域网访问", diff --git a/src/renderer/src/pages/substore.tsx b/src/renderer/src/pages/substore.tsx index 457baa9..2e1bdab 100644 --- a/src/renderer/src/pages/substore.tsx +++ b/src/renderer/src/pages/substore.tsx @@ -1,10 +1,19 @@ import { Button } from '@heroui/react' import BasePage from '@renderer/components/base/base-page' import { useAppConfig } from '@renderer/hooks/use-app-config' -import { subStoreFrontendPort, subStorePort } from '@renderer/utils/ipc' +import { + subStoreFrontendPort, + subStorePort, + startSubStoreFrontendServer, + startSubStoreBackendServer, + stopSubStoreFrontendServer, + stopSubStoreBackendServer, + downloadSubStore +} from '@renderer/utils/ipc' import React, { useEffect, useState } from 'react' import { HiExternalLink } from 'react-icons/hi' import { useTranslation } from 'react-i18next' +import { IoMdCloudDownload } from 'react-icons/io' const SubStore: React.FC = () => { const { t } = useTranslation() @@ -12,6 +21,7 @@ const SubStore: React.FC = () => { const { useCustomSubStore, customSubStoreUrl } = appConfig || {} const [backendPort, setBackendPort] = useState() const [frontendPort, setFrontendPort] = useState() + const [isUpdating, setIsUpdating] = useState(false) const getPort = async (): Promise => { setBackendPort(await subStorePort()) setFrontendPort(await subStoreFrontendPort()) @@ -27,20 +37,51 @@ const SubStore: React.FC = () => { { - open( - `http://127.0.0.1:${frontendPort}?api=${useCustomSubStore ? customSubStoreUrl : `http://127.0.0.1:${backendPort}`}` - ) - }} - > - - +
+ + +
} >