[fix] update restart process
This commit is contained in:
@@ -49,12 +49,26 @@ fn main() {
|
|||||||
let store_dir = config_dir.join(app_name).join("cstb");
|
let store_dir = config_dir.join(app_name).join("cstb");
|
||||||
|
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.plugin(tauri_plugin_single_instance::init(|app, _, _| {
|
.plugin(tauri_plugin_single_instance::init(
|
||||||
let window = app.get_webview_window("main").expect("no main window");
|
|app: &tauri::AppHandle, args: Vec<String>, _cwd: String| {
|
||||||
|
// 检查是否是"更新启动"的特殊请求
|
||||||
|
let is_update_launch = args.contains(&"--update-launch".to_string());
|
||||||
|
|
||||||
window.show().expect("no main window, can't show");
|
if is_update_launch {
|
||||||
window.set_focus().expect("no main window, can't set focus")
|
// 若存在旧实例,强制关闭(避免残留进程阻止新实例)
|
||||||
}))
|
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_deep_link::init())
|
||||||
.plugin(tauri_plugin_store::Builder::new().build())
|
.plugin(tauri_plugin_store::Builder::new().build())
|
||||||
.plugin(tauri_plugin_notification::init())
|
.plugin(tauri_plugin_notification::init())
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ use std::path::PathBuf;
|
|||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tauri::{Emitter, Manager};
|
|
||||||
use tauri::path::BaseDirectory;
|
use tauri::path::BaseDirectory;
|
||||||
|
use tauri::{Emitter, Manager};
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use std::os::windows::process::CommandExt;
|
use std::os::windows::process::CommandExt;
|
||||||
@@ -61,7 +61,6 @@ pub async fn check_update(
|
|||||||
github_repo: Option<&str>,
|
github_repo: Option<&str>,
|
||||||
include_prerelease: bool,
|
include_prerelease: bool,
|
||||||
) -> Result<Option<UpdateInfo>> {
|
) -> Result<Option<UpdateInfo>> {
|
||||||
|
|
||||||
// 确定使用的 API 端点
|
// 确定使用的 API 端点
|
||||||
let api_url = if let Some(custom_endpoint) = endpoint {
|
let api_url = if let Some(custom_endpoint) = endpoint {
|
||||||
// 如果提供了自定义端点,直接使用
|
// 如果提供了自定义端点,直接使用
|
||||||
@@ -70,13 +69,15 @@ pub async fn check_update(
|
|||||||
// 否则使用默认的 gh-info API
|
// 否则使用默认的 gh-info API
|
||||||
let repo = github_repo.unwrap_or("plsgo/cstb");
|
let repo = github_repo.unwrap_or("plsgo/cstb");
|
||||||
if include_prerelease {
|
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 {
|
} else {
|
||||||
format!("https://gh-info.okk.cool/repos/{}/releases/latest", repo)
|
format!("https://gh-info.okk.cool/repos/{}/releases/latest", repo)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
let client = reqwest::Client::builder()
|
let client = reqwest::Client::builder()
|
||||||
.timeout(std::time::Duration::from_secs(10))
|
.timeout(std::time::Duration::from_secs(10))
|
||||||
.user_agent("cstb-updater/1.0")
|
.user_agent("cstb-updater/1.0")
|
||||||
@@ -85,7 +86,10 @@ pub async fn check_update(
|
|||||||
let response = client.get(&api_url).send().await?;
|
let response = client.get(&api_url).send().await?;
|
||||||
|
|
||||||
if !response.status().is_success() {
|
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);
|
// println!("[更新检查] API 响应: {}", response_text);
|
||||||
|
|
||||||
// 尝试解析为自定义更新服务器格式
|
// 尝试解析为自定义更新服务器格式
|
||||||
let update_info = if let Ok(custom_resp) = serde_json::from_str::<CustomUpdateApiResponse>(&response_text) {
|
let update_info = if let Ok(custom_resp) =
|
||||||
|
serde_json::from_str::<CustomUpdateApiResponse>(&response_text)
|
||||||
|
{
|
||||||
// 提取版本号(去掉 'v' 前缀)
|
// 提取版本号(去掉 'v' 前缀)
|
||||||
let version = custom_resp.version.trim_start_matches('v').to_string();
|
let version = custom_resp.version.trim_start_matches('v').to_string();
|
||||||
|
|
||||||
@@ -118,7 +123,8 @@ pub async fn check_update(
|
|||||||
let platform_key = "";
|
let platform_key = "";
|
||||||
|
|
||||||
if !platform_key.is_empty() {
|
if !platform_key.is_empty() {
|
||||||
platforms.get(platform_key)
|
platforms
|
||||||
|
.get(platform_key)
|
||||||
.map(|p| p.url.clone())
|
.map(|p| p.url.clone())
|
||||||
.unwrap_or_else(|| custom_resp.download_url.clone())
|
.unwrap_or_else(|| custom_resp.download_url.clone())
|
||||||
} else {
|
} else {
|
||||||
@@ -148,7 +154,6 @@ pub async fn check_update(
|
|||||||
let comparison = compare_version(&version, current_version);
|
let comparison = compare_version(&version, current_version);
|
||||||
|
|
||||||
if comparison > 0 {
|
if comparison > 0 {
|
||||||
|
|
||||||
// 从 attachments 中获取下载链接
|
// 从 attachments 中获取下载链接
|
||||||
// 支持两种格式:
|
// 支持两种格式:
|
||||||
// 1. 字符串数组: ["URL1", "URL2", ...]
|
// 1. 字符串数组: ["URL1", "URL2", ...]
|
||||||
@@ -177,9 +182,10 @@ fn extract_download_url(attachments: &serde_json::Value) -> Option<String> {
|
|||||||
// 尝试解析为字符串数组格式: ["URL1", "URL2", ...]
|
// 尝试解析为字符串数组格式: ["URL1", "URL2", ...]
|
||||||
if let Ok(urls) = serde_json::from_value::<Vec<String>>(attachments.clone()) {
|
if let Ok(urls) = serde_json::from_value::<Vec<String>>(attachments.clone()) {
|
||||||
// 优先选择 .exe 或 .msi 文件
|
// 优先选择 .exe 或 .msi 文件
|
||||||
if let Some(url) = urls.iter().find(|url| {
|
if let Some(url) = urls
|
||||||
url.ends_with(".exe") || url.ends_with(".msi")
|
.iter()
|
||||||
}) {
|
.find(|url| url.ends_with(".exe") || url.ends_with(".msi"))
|
||||||
|
{
|
||||||
return Some(url.clone());
|
return Some(url.clone());
|
||||||
}
|
}
|
||||||
// 如果没有找到 .exe 或 .msi,使用第一个 URL
|
// 如果没有找到 .exe 或 .msi,使用第一个 URL
|
||||||
@@ -217,12 +223,10 @@ fn extract_download_url(attachments: &serde_json::Value) -> Option<String> {
|
|||||||
|
|
||||||
/// 改进的版本比较函数,支持预发布版本(beta.5, beta.6等)
|
/// 改进的版本比较函数,支持预发布版本(beta.5, beta.6等)
|
||||||
fn compare_version(new: &str, current: &str) -> i32 {
|
fn compare_version(new: &str, current: &str) -> i32 {
|
||||||
|
|
||||||
// 解析版本号:支持格式如 "0.0.6-beta.5", "beta.6", "0.0.6" 等
|
// 解析版本号:支持格式如 "0.0.6-beta.5", "beta.6", "0.0.6" 等
|
||||||
let (new_base, new_pre) = parse_version(new);
|
let (new_base, new_pre) = parse_version(new);
|
||||||
let (current_base, current_pre) = parse_version(current);
|
let (current_base, current_pre) = parse_version(current);
|
||||||
|
|
||||||
|
|
||||||
// 先比较基础版本号(数字部分)
|
// 先比较基础版本号(数字部分)
|
||||||
let base_comparison = compare_version_parts(&new_base, ¤t_base);
|
let base_comparison = compare_version_parts(&new_base, ¤t_base);
|
||||||
|
|
||||||
@@ -256,10 +260,7 @@ fn parse_version(version: &str) -> (Vec<u32>, Option<String>) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 解析基础版本号(数字部分)
|
// 解析基础版本号(数字部分)
|
||||||
let base_parts: Vec<u32> = base_str
|
let base_parts: Vec<u32> = base_str.split('.').filter_map(|s| s.parse().ok()).collect();
|
||||||
.split('.')
|
|
||||||
.filter_map(|s| s.parse().ok())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// 如果基础版本号为空且没有预发布标识符,可能是纯预发布版本(如 "beta.5")
|
// 如果基础版本号为空且没有预发布标识符,可能是纯预发布版本(如 "beta.5")
|
||||||
// 这种情况下,整个字符串作为预发布标识符
|
// 这种情况下,整个字符串作为预发布标识符
|
||||||
@@ -350,7 +351,10 @@ pub async fn download_update(
|
|||||||
let response = client.get(download_url).send().await?;
|
let response = client.get(download_url).send().await?;
|
||||||
|
|
||||||
if !response.status().is_success() {
|
if !response.status().is_success() {
|
||||||
return Err(anyhow::anyhow!("下载失败,HTTP 状态码: {}", response.status()));
|
return Err(anyhow::anyhow!(
|
||||||
|
"下载失败,HTTP 状态码: {}",
|
||||||
|
response.status()
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取文件总大小
|
// 获取文件总大小
|
||||||
@@ -417,7 +421,7 @@ pub async fn download_update(
|
|||||||
pub fn install_update(installer_path: &str) -> Result<()> {
|
pub fn install_update(installer_path: &str) -> Result<()> {
|
||||||
// 使用 /S 静默安装
|
// 使用 /S 静默安装
|
||||||
let mut cmd = Command::new(installer_path);
|
let mut cmd = Command::new(installer_path);
|
||||||
cmd.args(&["/S"]); // 静默安装
|
cmd.args(&["/S", "/appParam=\"--update-launch\""]); // 静默安装
|
||||||
cmd.creation_flags(CREATE_NO_WINDOW);
|
cmd.creation_flags(CREATE_NO_WINDOW);
|
||||||
let mut child = cmd.spawn()?;
|
let mut child = cmd.spawn()?;
|
||||||
// 等待安装程序完成
|
// 等待安装程序完成
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useState, useEffect } from "react"
|
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 { Download, Refresh, FileText, Close, Check } from "@icon-park/react"
|
||||||
import { invoke } from "@tauri-apps/api/core"
|
import { invoke } from "@tauri-apps/api/core"
|
||||||
import { listen } from "@tauri-apps/api/event"
|
import { listen } from "@tauri-apps/api/event"
|
||||||
@@ -22,7 +31,11 @@ interface UpdateCheckerProps {
|
|||||||
includePrerelease?: boolean
|
includePrerelease?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function UpdateChecker({ useMirror = true, customEndpoint, includePrerelease = false }: UpdateCheckerProps) {
|
export function UpdateChecker({
|
||||||
|
useMirror = true,
|
||||||
|
customEndpoint,
|
||||||
|
includePrerelease = false,
|
||||||
|
}: UpdateCheckerProps) {
|
||||||
const app = useAppStore()
|
const app = useAppStore()
|
||||||
const [checking, setChecking] = useState(false)
|
const [checking, setChecking] = useState(false)
|
||||||
const [downloading, setDownloading] = useState(false)
|
const [downloading, setDownloading] = useState(false)
|
||||||
@@ -30,7 +43,11 @@ export function UpdateChecker({ useMirror = true, customEndpoint, includePrerele
|
|||||||
const [downloadProgress, setDownloadProgress] = useState(0)
|
const [downloadProgress, setDownloadProgress] = useState(0)
|
||||||
const [installerPath, setInstallerPath] = useState<string | null>(null)
|
const [installerPath, setInstallerPath] = useState<string | null>(null)
|
||||||
const [downloadCompleted, setDownloadCompleted] = useState(false)
|
const [downloadCompleted, setDownloadCompleted] = useState(false)
|
||||||
const { isOpen: isChangelogOpen, onOpen: onChangelogOpen, onOpenChange: onChangelogOpenChange } = useDisclosure()
|
const {
|
||||||
|
isOpen: isChangelogOpen,
|
||||||
|
onOpen: onChangelogOpen,
|
||||||
|
onOpenChange: onChangelogOpenChange,
|
||||||
|
} = useDisclosure()
|
||||||
|
|
||||||
// 监听下载进度事件
|
// 监听下载进度事件
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -46,7 +63,7 @@ export function UpdateChecker({ useMirror = true, customEndpoint, includePrerele
|
|||||||
})
|
})
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unlisten.then(fn => fn())
|
unlisten.then((fn) => fn())
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@@ -182,7 +199,7 @@ export function UpdateChecker({ useMirror = true, customEndpoint, includePrerele
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 安装完成后,等待一小段时间确保安装程序完全退出
|
// 安装完成后,等待一小段时间确保安装程序完全退出
|
||||||
await new Promise(resolve => setTimeout(resolve, 500))
|
await new Promise((resolve) => setTimeout(resolve, 1500))
|
||||||
|
|
||||||
// 启动新版本
|
// 启动新版本
|
||||||
await relaunch()
|
await relaunch()
|
||||||
@@ -267,9 +284,7 @@ export function UpdateChecker({ useMirror = true, customEndpoint, includePrerele
|
|||||||
{downloadCompleted ? (
|
{downloadCompleted ? (
|
||||||
<>
|
<>
|
||||||
<Check className="text-green-500 dark:text-green-400" size={14} />
|
<Check className="text-green-500 dark:text-green-400" size={14} />
|
||||||
<span className="text-xs text-green-500 dark:text-green-400">
|
<span className="text-xs text-green-500 dark:text-green-400">下载完成</span>
|
||||||
下载完成
|
|
||||||
</span>
|
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ export interface MemoryInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface MonitorInfo {
|
export interface MonitorInfo {
|
||||||
|
manufacturer?: string
|
||||||
|
model?: string
|
||||||
name?: string
|
name?: string
|
||||||
refresh_rate?: number // Hz
|
refresh_rate?: number // Hz
|
||||||
resolution_width?: number
|
resolution_width?: number
|
||||||
|
|||||||
Reference in New Issue
Block a user