From 4c151c3dd527df487ec1fc4cdacc6d0fdacf0f9d Mon Sep 17 00:00:00 2001 From: purp1e Date: Sat, 8 Nov 2025 20:56:02 +0800 Subject: [PATCH] [fix] update restart process --- src-tauri/src/main.rs | 24 +++++++-- src-tauri/src/tool/updater.rs | 76 ++++++++++++++------------- src/components/cstb/UpdateChecker.tsx | 37 +++++++++---- src/store/hardware.ts | 2 + 4 files changed, 87 insertions(+), 52 deletions(-) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 62525f4..f2e195a 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -49,12 +49,26 @@ fn main() { let store_dir = config_dir.join(app_name).join("cstb"); tauri::Builder::default() - .plugin(tauri_plugin_single_instance::init(|app, _, _| { - let window = app.get_webview_window("main").expect("no main window"); + .plugin(tauri_plugin_single_instance::init( + |app: &tauri::AppHandle, args: Vec, _cwd: String| { + // 检查是否是"更新启动"的特殊请求 + let is_update_launch = args.contains(&"--update-launch".to_string()); - window.show().expect("no main window, can't show"); - window.set_focus().expect("no main window, can't set focus") - })) + if is_update_launch { + // 若存在旧实例,强制关闭(避免残留进程阻止新实例) + if let Some(old_window) = app.get_webview_window("main") { + // 先尝试优雅关闭,再强制退出 + old_window.close().ok(); + app.exit(0); + } + } else { + // 常规单实例逻辑:激活现有窗口 + let window = app.get_webview_window("main").expect("no main window"); + window.show().expect("can't show main window"); + window.set_focus().expect("can't focus main window"); + } + }, + )) .plugin(tauri_plugin_deep_link::init()) .plugin(tauri_plugin_store::Builder::new().build()) .plugin(tauri_plugin_notification::init()) diff --git a/src-tauri/src/tool/updater.rs b/src-tauri/src/tool/updater.rs index 7585c79..93b242d 100644 --- a/src-tauri/src/tool/updater.rs +++ b/src-tauri/src/tool/updater.rs @@ -5,8 +5,8 @@ use std::path::PathBuf; use std::process::Command; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; -use tauri::{Emitter, Manager}; use tauri::path::BaseDirectory; +use tauri::{Emitter, Manager}; #[cfg(windows)] use std::os::windows::process::CommandExt; @@ -61,7 +61,6 @@ pub async fn check_update( github_repo: Option<&str>, include_prerelease: bool, ) -> Result> { - // 确定使用的 API 端点 let api_url = if let Some(custom_endpoint) = endpoint { // 如果提供了自定义端点,直接使用 @@ -70,12 +69,14 @@ pub async fn check_update( // 否则使用默认的 gh-info API let repo = github_repo.unwrap_or("plsgo/cstb"); if include_prerelease { - format!("https://gh-info.okk.cool/repos/{}/releases/latest/pre", repo) + format!( + "https://gh-info.okk.cool/repos/{}/releases/latest/pre", + repo + ) } else { format!("https://gh-info.okk.cool/repos/{}/releases/latest", repo) } }; - let client = reqwest::Client::builder() .timeout(std::time::Duration::from_secs(10)) @@ -85,7 +86,10 @@ pub async fn check_update( let response = client.get(&api_url).send().await?; if !response.status().is_success() { - return Err(anyhow::anyhow!("API 请求失败,HTTP 状态码: {}", response.status())); + return Err(anyhow::anyhow!( + "API 请求失败,HTTP 状态码: {}", + response.status() + )); } // 获取响应文本以便尝试不同的解析方式 @@ -94,8 +98,9 @@ pub async fn check_update( // println!("[更新检查] API 响应: {}", response_text); // 尝试解析为自定义更新服务器格式 - let update_info = if let Ok(custom_resp) = serde_json::from_str::(&response_text) { - + let update_info = if let Ok(custom_resp) = + serde_json::from_str::(&response_text) + { // 提取版本号(去掉 'v' 前缀) let version = custom_resp.version.trim_start_matches('v').to_string(); @@ -118,7 +123,8 @@ pub async fn check_update( let platform_key = ""; if !platform_key.is_empty() { - platforms.get(platform_key) + platforms + .get(platform_key) .map(|p| p.url.clone()) .unwrap_or_else(|| custom_resp.download_url.clone()) } else { @@ -148,7 +154,6 @@ pub async fn check_update( let comparison = compare_version(&version, current_version); if comparison > 0 { - // 从 attachments 中获取下载链接 // 支持两种格式: // 1. 字符串数组: ["URL1", "URL2", ...] @@ -177,15 +182,16 @@ fn extract_download_url(attachments: &serde_json::Value) -> Option { // 尝试解析为字符串数组格式: ["URL1", "URL2", ...] if let Ok(urls) = serde_json::from_value::>(attachments.clone()) { // 优先选择 .exe 或 .msi 文件 - if let Some(url) = urls.iter().find(|url| { - url.ends_with(".exe") || url.ends_with(".msi") - }) { + if let Some(url) = urls + .iter() + .find(|url| url.ends_with(".exe") || url.ends_with(".msi")) + { return Some(url.clone()); } // 如果没有找到 .exe 或 .msi,使用第一个 URL return urls.first().cloned(); } - + // 尝试解析为嵌套数组格式: [["文件名", "URL"], ...] if let Ok(nested) = serde_json::from_value::>>(attachments.clone()) { // 优先选择 .exe 或 .msi 文件 @@ -211,34 +217,32 @@ fn extract_download_url(attachments: &serde_json::Value) -> Option { } } } - + None } /// 改进的版本比较函数,支持预发布版本(beta.5, beta.6等) fn compare_version(new: &str, current: &str) -> i32 { - // 解析版本号:支持格式如 "0.0.6-beta.5", "beta.6", "0.0.6" 等 let (new_base, new_pre) = parse_version(new); let (current_base, current_pre) = parse_version(current); - - + // 先比较基础版本号(数字部分) let base_comparison = compare_version_parts(&new_base, ¤t_base); - + if base_comparison != 0 { return base_comparison; } - + // 如果基础版本相同(或都为空),比较预发布标识符 // 如果基础版本都为空,说明是纯预发布版本(如 beta.5 vs beta.6) let pre_comparison = compare_prerelease(&new_pre, ¤t_pre); - + // 如果基础版本都为空且预发布比较结果为0,说明版本完全相同 if new_base.is_empty() && current_base.is_empty() && pre_comparison == 0 { return 0; } - + pre_comparison } @@ -246,7 +250,7 @@ fn compare_version(new: &str, current: &str) -> i32 { fn parse_version(version: &str) -> (Vec, Option) { // 去掉 'v' 前缀 let version = version.trim_start_matches('v').trim(); - + // 检查是否有预发布标识符(如 -beta.5, -alpha.1 等) let (base_str, pre_str) = if let Some(dash_pos) = version.find('-') { let (base, pre) = version.split_at(dash_pos); @@ -254,13 +258,10 @@ fn parse_version(version: &str) -> (Vec, Option) { } else { (version, None) }; - + // 解析基础版本号(数字部分) - let base_parts: Vec = base_str - .split('.') - .filter_map(|s| s.parse().ok()) - .collect(); - + let base_parts: Vec = base_str.split('.').filter_map(|s| s.parse().ok()).collect(); + // 如果基础版本号为空且没有预发布标识符,可能是纯预发布版本(如 "beta.5") // 这种情况下,整个字符串作为预发布标识符 if base_parts.is_empty() && pre_str.is_none() { @@ -269,25 +270,25 @@ fn parse_version(version: &str) -> (Vec, Option) { return (vec![], Some(version.to_string())); } } - + (base_parts, pre_str) } /// 比较版本号数组(数字部分) fn compare_version_parts(new: &[u32], current: &[u32]) -> i32 { let max_len = new.len().max(current.len()); - + for i in 0..max_len { let new_val = new.get(i).copied().unwrap_or(0); let current_val = current.get(i).copied().unwrap_or(0); - + if new_val > current_val { return 1; } else if new_val < current_val { return -1; } } - + 0 } @@ -308,7 +309,7 @@ fn compare_prerelease(new: &Option, current: &Option) -> i32 { // 尝试提取数字部分进行比较(如 beta.5 -> 5, beta.6 -> 6) let new_num = extract_number_from_prerelease(new_pre); let current_num = extract_number_from_prerelease(current_pre); - + if let (Some(new_n), Some(current_n)) = (new_num, current_num) { // 如果都能提取数字,比较数字 if new_n > current_n { @@ -350,7 +351,10 @@ pub async fn download_update( let response = client.get(download_url).send().await?; if !response.status().is_success() { - return Err(anyhow::anyhow!("下载失败,HTTP 状态码: {}", response.status())); + return Err(anyhow::anyhow!( + "下载失败,HTTP 状态码: {}", + response.status() + )); } // 获取文件总大小 @@ -374,7 +378,7 @@ pub async fn download_update( .unwrap_or("update"); let file_path = cache_dir.join(filename); - + // 下载文件 let mut file = fs::File::create(&file_path)?; let mut stream = response.bytes_stream(); @@ -417,7 +421,7 @@ pub async fn download_update( pub fn install_update(installer_path: &str) -> Result<()> { // 使用 /S 静默安装 let mut cmd = Command::new(installer_path); - cmd.args(&["/S"]); // 静默安装 + cmd.args(&["/S", "/appParam=\"--update-launch\""]); // 静默安装 cmd.creation_flags(CREATE_NO_WINDOW); let mut child = cmd.spawn()?; // 等待安装程序完成 diff --git a/src/components/cstb/UpdateChecker.tsx b/src/components/cstb/UpdateChecker.tsx index 9ea3309..65a405e 100644 --- a/src/components/cstb/UpdateChecker.tsx +++ b/src/components/cstb/UpdateChecker.tsx @@ -1,7 +1,16 @@ "use client" import { useState, useEffect } from "react" -import { Button, CircularProgress, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, useDisclosure } from "@heroui/react" +import { + Button, + CircularProgress, + Modal, + ModalContent, + ModalHeader, + ModalBody, + ModalFooter, + useDisclosure, +} from "@heroui/react" import { Download, Refresh, FileText, Close, Check } from "@icon-park/react" import { invoke } from "@tauri-apps/api/core" import { listen } from "@tauri-apps/api/event" @@ -22,7 +31,11 @@ interface UpdateCheckerProps { includePrerelease?: boolean } -export function UpdateChecker({ useMirror = true, customEndpoint, includePrerelease = false }: UpdateCheckerProps) { +export function UpdateChecker({ + useMirror = true, + customEndpoint, + includePrerelease = false, +}: UpdateCheckerProps) { const app = useAppStore() const [checking, setChecking] = useState(false) const [downloading, setDownloading] = useState(false) @@ -30,14 +43,18 @@ export function UpdateChecker({ useMirror = true, customEndpoint, includePrerele const [downloadProgress, setDownloadProgress] = useState(0) const [installerPath, setInstallerPath] = useState(null) const [downloadCompleted, setDownloadCompleted] = useState(false) - const { isOpen: isChangelogOpen, onOpen: onChangelogOpen, onOpenChange: onChangelogOpenChange } = useDisclosure() + const { + isOpen: isChangelogOpen, + onOpen: onChangelogOpen, + onOpenChange: onChangelogOpenChange, + } = useDisclosure() // 监听下载进度事件 useEffect(() => { const unlisten = listen("update-download-progress", (event) => { const progress = event.payload setDownloadProgress(progress) - + // 如果进度达到 100%,标记下载完成 if (progress === 100) { setDownloading(false) @@ -46,7 +63,7 @@ export function UpdateChecker({ useMirror = true, customEndpoint, includePrerele }) return () => { - unlisten.then(fn => fn()) + unlisten.then((fn) => fn()) } }, []) @@ -122,7 +139,7 @@ export function UpdateChecker({ useMirror = true, customEndpoint, includePrerele setDownloadProgress(100) setDownloading(false) setDownloadCompleted(true) - + addToast({ title: "下载完成", description: "可以点击安装按钮进行安装", @@ -182,8 +199,8 @@ export function UpdateChecker({ useMirror = true, customEndpoint, includePrerele }) // 安装完成后,等待一小段时间确保安装程序完全退出 - await new Promise(resolve => setTimeout(resolve, 500)) - + await new Promise((resolve) => setTimeout(resolve, 1500)) + // 启动新版本 await relaunch() } catch (error) { @@ -267,9 +284,7 @@ export function UpdateChecker({ useMirror = true, customEndpoint, includePrerele {downloadCompleted ? ( <> - - 下载完成 - + 下载完成 ) : ( <> diff --git a/src/store/hardware.ts b/src/store/hardware.ts index dc8baa0..434a4d7 100644 --- a/src/store/hardware.ts +++ b/src/store/hardware.ts @@ -32,6 +32,8 @@ export interface MemoryInfo { } export interface MonitorInfo { + manufacturer?: string + model?: string name?: string refresh_rate?: number // Hz resolution_width?: number