fix: update sub-store on linux (#545)

thanks
This commit is contained in:
Alan Lin 2025-03-03 09:09:09 +08:00 committed by GitHub
parent 4b238d4dc2
commit f30596231b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 129 additions and 53 deletions

View File

@ -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<void> {
}
}
export async function downloadSubStore(): Promise<void> {
export async function downloadSubStore(password?: string): Promise<void> {
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<void> {
}
}
)
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<void> {
}
}
)
// 创建临时目录
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 })

View File

@ -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)

View File

@ -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<number | undefined>()
const [frontendPort, setFrontendPort] = useState<number | undefined>()
const [isUpdating, setIsUpdating] = useState(false)
const [openPasswordModal, setOpenPasswordModal] = useState(false)
const getPort = async (): Promise<void> => {
setBackendPort(await subStorePort())
setFrontendPort(await subStoreFrontendPort())
@ -34,39 +37,85 @@ const SubStore: React.FC = () => {
if (!frontendPort) return null
return (
<>
{openPasswordModal && (
<BasePasswordModal
onCancel={() => 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)
}
}}
/>
)}
<BasePage
title={t('substore.title')}
header={
<div className="flex gap-2">
<Button
title={t('substore.checkUpdate')}
isIconOnly
size="sm"
className="app-nodrag"
variant="light"
isLoading={isUpdating}
onPress={async () => {
try {
new Notification(t('substore.updating'))
setIsUpdating(true)
await downloadSubStore()
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) {
new Notification(`${t('substore.updateFailed')}: ${e}`)
} finally {
setIsUpdating(false)
}
}}
>
<IoMdCloudDownload className="text-lg" />
</Button>
{platform != 'linux' && (
<Button
title={t('substore.checkUpdate')}
isIconOnly
size="sm"
className="app-nodrag"
variant="light"
isLoading={isUpdating}
onPress={async () => {
try {
new Notification(t('substore.updating'))
setIsUpdating(true)
await downloadSubStore()
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) {
new Notification(`${t('substore.updateFailed')}: ${e}`)
} finally {
setIsUpdating(false)
}
}}
>
<IoMdCloudDownload className="text-lg" />
</Button>
)}
{platform === 'linux' && (
<Button
title={t('substore.checkUpdate')}
isIconOnly
size="sm"
className="app-nodrag"
variant="light"
isLoading={isUpdating}
onPress={async () => {
try {
setIsUpdating(true)
setOpenPasswordModal(true)
} catch (e) {
new Notification(`${t('substore.updateFailed')}: ${e}`)
} finally {
setIsUpdating(false)
}
}}
>
<IoMdCloudDownload className="text-lg" />
</Button>
)}
<Button
title={t('substore.openInBrowser')}
isIconOnly

View File

@ -326,8 +326,8 @@ export async function startSubStoreBackendServer(): Promise<void> {
export async function stopSubStoreBackendServer(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('stopSubStoreBackendServer'))
}
export async function downloadSubStore(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('downloadSubStore'))
export async function downloadSubStore(password?: string): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('downloadSubStore', password))
}
export async function subStorePort(): Promise<number> {