[feat] more hw info and update feature

This commit is contained in:
2025-11-08 18:09:35 +08:00
parent 41105d3bab
commit c8d8339f30
13 changed files with 813 additions and 500 deletions

View File

@@ -9,6 +9,8 @@ use std::fs::File;
use std::fs;
use std::path::Path;
use std::io::{BufRead, BufReader};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, OnceLock};
use tauri::path::BaseDirectory;
use tauri::Manager;
use serde::{Deserialize, Serialize};
@@ -17,6 +19,13 @@ use serde::{Deserialize, Serialize};
// pub type Result<T, String = ()> = Result<T, String>;
// 全局下载取消标志
static DOWNLOAD_CANCELLED: OnceLock<Arc<AtomicBool>> = OnceLock::new();
fn get_download_cancelled() -> Arc<AtomicBool> {
DOWNLOAD_CANCELLED.get_or_init(|| Arc::new(AtomicBool::new(false))).clone()
}
#[tauri::command]
pub fn greet(name: &str) -> Result<String, String> {
Ok(format!("Hello, {}! You've been greeted from Rust!", name))
@@ -301,20 +310,46 @@ pub fn read_vprof_report(console_log_path: &str) -> Result<String, String> {
// 更新相关命令
/// 检查更新
/// 检查更新(支持 GitHub Release 和自定义端点)
#[tauri::command]
pub async fn check_app_update(
app: tauri::AppHandle,
custom_endpoint: Option<String>,
github_repo: Option<String>,
endpoint: Option<String>,
use_mirror: Option<bool>,
include_prerelease: Option<bool>,
) -> Result<Option<UpdateInfo>, String> {
let current_version = app.package_info().version.to_string();
let use_mirror = use_mirror.unwrap_or(false);
let include_prerelease = include_prerelease.unwrap_or(false);
wrap_err!(check_update(
custom_endpoint.as_deref(),
github_repo.as_deref(),
&current_version
).await)
println!("[检查更新命令] 当前应用版本: {}", current_version);
println!("[检查更新命令] 使用镜像: {}", use_mirror);
println!("[检查更新命令] 包含预发布版本: {}", include_prerelease);
if let Some(ref ep) = endpoint {
println!("[检查更新命令] 自定义端点: {}", ep);
}
// 从环境变量获取 GitHub 仓库信息,如果没有则使用默认值
const DEFAULT_GITHUB_REPO: &str = "plsgo/cstb";
let github_repo_str = std::env::var("GITHUB_REPO").ok();
let github_repo = github_repo_str.as_deref().unwrap_or(DEFAULT_GITHUB_REPO);
println!("[检查更新命令] GitHub 仓库: {}", github_repo);
let result = wrap_err!(check_update(
endpoint.as_deref(),
&current_version,
use_mirror,
Some(github_repo),
include_prerelease
).await)?;
if result.is_some() {
println!("[检查更新命令] ✓ 返回更新信息");
} else {
println!("[检查更新命令] ✗ 无更新可用");
}
Ok(result)
}
/// 下载更新
@@ -323,15 +358,22 @@ pub async fn download_app_update(
app: tauri::AppHandle,
download_url: String,
) -> Result<String, String> {
let path = wrap_err!(download_update(
&app,
&download_url,
None // 可以添加进度回调
).await)?;
// 重置取消标志
let cancelled = get_download_cancelled();
cancelled.store(false, Ordering::Relaxed);
let path = wrap_err!(download_update(&app, &download_url, cancelled).await)?;
Ok(path.to_string_lossy().to_string())
}
/// 取消下载
#[tauri::command]
pub fn cancel_download_update() -> Result<(), String> {
let cancelled = get_download_cancelled();
cancelled.store(true, Ordering::Relaxed);
Ok(())
}
/// 安装更新
#[tauri::command]
pub fn install_app_update(installer_path: String) -> Result<(), String> {
@@ -638,7 +680,7 @@ pub async fn get_monitor_info() -> Result<Vec<MonitorInfo>, String> {
}
// 尝试获取刷新率和分辨率信息
let display_output = Command::new("powershell")
let _display_output = Command::new("powershell")
.args(&[
"-NoProfile",
"-Command",
@@ -648,7 +690,7 @@ pub async fn get_monitor_info() -> Result<Vec<MonitorInfo>, String> {
.await;
// 获取刷新率信息
let refresh_output = Command::new("powershell")
let _refresh_output = Command::new("powershell")
.args(&[
"-NoProfile",
"-Command",

View File

@@ -168,6 +168,7 @@ fn main() {
cmds::read_vprof_report,
cmds::check_app_update,
cmds::download_app_update,
cmds::cancel_download_update,
cmds::install_app_update,
cmds::get_computer_info,
cmds::get_gpu_info,

View File

@@ -3,8 +3,10 @@ use serde::{Deserialize, Serialize};
use std::fs;
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::Manager;
#[cfg(windows)]
use std::os::windows::process::CommandExt;
@@ -17,303 +19,360 @@ const CREATE_NO_WINDOW: u32 = 0x08000000;
pub struct UpdateInfo {
pub version: String,
pub notes: Option<String>,
pub pub_date: Option<String>,
pub download_url: String,
pub signature: Option<String>,
}
/// 自定义更新服务器响应格式
#[derive(Debug, Deserialize)]
struct CustomUpdateResponse {
/// gh-info API 响应结构
#[derive(Debug, Clone, Serialize, Deserialize)]
struct GhInfoApiResponse {
repo: String,
latest_version: String,
changelog: Option<String>,
published_at: String,
#[serde(default)]
prerelease: bool,
attachments: serde_json::Value, // 支持两种格式: ["URL1", "URL2"] 或 [["文件名", "URL"], ...]
}
/// 自定义更新服务器 API 响应结构
#[derive(Debug, Clone, Serialize, Deserialize)]
struct CustomUpdateApiResponse {
version: String,
notes: Option<String>,
#[serde(rename = "pub_date")]
pub_date: Option<String>,
download_url: String,
signature: Option<String>,
platforms: Option<PlatformDownloads>,
platforms: Option<std::collections::HashMap<String, PlatformInfo>>,
}
/// 平台特定的下载链接
#[derive(Debug, Deserialize)]
struct PlatformDownloads {
#[serde(rename = "windows-x86_64")]
windows_x86_64: Option<PlatformInfo>,
#[serde(rename = "darwin-x86_64")]
darwin_x86_64: Option<PlatformInfo>,
#[serde(rename = "darwin-aarch64")]
darwin_aarch64: Option<PlatformInfo>,
#[serde(rename = "linux-x86_64")]
linux_x86_64: Option<PlatformInfo>,
}
#[derive(Debug, Deserialize)]
/// 平台特定信息
#[derive(Debug, Clone, Serialize, Deserialize)]
struct PlatformInfo {
url: String,
signature: Option<String>,
}
/// GitHub Release API 响应
#[derive(Debug, Deserialize)]
struct GitHubRelease {
tag_name: String,
name: Option<String>,
body: Option<String>,
published_at: Option<String>,
assets: Vec<GitHubAsset>,
}
#[derive(Debug, Deserialize)]
struct GitHubAsset {
name: String,
browser_download_url: String,
content_type: String,
}
/// 检查更新
///
/// # 参数
/// - `custom_endpoint`: 自定义更新服务器端点 URL可选
/// - `github_repo`: GitHub 仓库格式owner/repo可选
/// - `current_version`: 当前应用版本
///
/// # 返回
/// 如果有更新,返回 UpdateInfo否则返回 None
/// 检查更新(使用自定义 API 端点)
pub async fn check_update(
custom_endpoint: Option<&str>,
github_repo: Option<&str>,
endpoint: Option<&str>,
current_version: &str,
_use_mirror: bool,
github_repo: Option<&str>,
include_prerelease: bool,
) -> Result<Option<UpdateInfo>> {
// 首先尝试自定义服务器
if let Some(endpoint) = custom_endpoint {
if !endpoint.is_empty() {
match check_custom_update(endpoint).await {
Ok(Some(info)) => {
if compare_versions(&info.version, current_version) > 0 {
return Ok(Some(info));
}
}
Err(e) => {
log::warn!("自定义更新服务器检查失败: {}", e);
}
Ok(None) => {}
}
println!("[更新检查] 开始检查更新...");
println!("[更新检查] 当前版本: {}", current_version);
println!("[更新检查] 包含预发布版本: {}", include_prerelease);
// 确定使用的 API 端点
let api_url = if let Some(custom_endpoint) = endpoint {
// 如果提供了自定义端点,直接使用
custom_endpoint.to_string()
} else {
// 否则使用默认的 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)
} else {
format!("https://gh-info.okk.cool/repos/{}/releases/latest", repo)
}
}
};
println!("[更新检查] API URL: {}", api_url);
// Fallback 到 GitHub Release
if let Some(repo) = github_repo {
match check_github_update(repo).await {
Ok(Some(info)) => {
if compare_versions(&info.version, current_version) > 0 {
return Ok(Some(info));
}
}
Err(e) => {
log::warn!("GitHub Release 检查失败: {}", e);
}
Ok(None) => {}
}
}
Ok(None)
}
/// 检查自定义更新服务器
async fn check_custom_update(endpoint: &str) -> Result<Option<UpdateInfo>> {
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(10))
.user_agent("cstb-updater/1.0")
.build()?;
let response = client.get(endpoint).send().await?;
let response = client.get(&api_url).send().await?;
if !response.status().is_success() {
return Err(anyhow::anyhow!("HTTP 状态码: {}", response.status()));
println!("[更新检查] ✗ API 请求失败,HTTP 状态码: {}", response.status());
return Err(anyhow::anyhow!("API 请求失败HTTP 状态码: {}", response.status()));
}
let text = response.text().await?;
let update_resp: CustomUpdateResponse = serde_json::from_str(&text)
.context("解析自定义更新服务器响应失败")?;
// 获取响应文本以便尝试不同的解析方式
let response_text = response.text().await?;
// 关闭更新日志的打印
// println!("[更新检查] API 响应: {}", response_text);
// 获取平台特定的下载链接
let download_url = if let Some(platforms) = update_resp.platforms {
#[cfg(target_os = "windows")]
{
#[cfg(target_arch = "x86_64")]
{
platforms
.windows_x86_64
.map(|p| p.url)
.unwrap_or(update_resp.download_url)
}
}
#[cfg(target_os = "macos")]
{
#[cfg(target_arch = "x86_64")]
{
platforms
.darwin_x86_64
.map(|p| p.url)
.unwrap_or(update_resp.download_url)
}
#[cfg(target_arch = "aarch64")]
{
platforms
.darwin_aarch64
.map(|p| p.url)
.unwrap_or(update_resp.download_url)
}
}
#[cfg(target_os = "linux")]
{
#[cfg(target_arch = "x86_64")]
{
platforms
.linux_x86_64
.map(|p| p.url)
.unwrap_or(update_resp.download_url)
}
// 尝试解析为自定义更新服务器格式
let update_info = if let Ok(custom_resp) = serde_json::from_str::<CustomUpdateApiResponse>(&response_text) {
println!("[更新检查] 检测到自定义更新服务器格式");
// 提取版本号(去掉 'v' 前缀)
let version = custom_resp.version.trim_start_matches('v').to_string();
println!("[更新检查] 远程版本: {}", version);
// 版本比较
let comparison = compare_version(&version, current_version);
println!("[更新检查] 版本比较结果: {} (1=有新版本, 0=相同, -1=当前更新)", comparison);
if comparison > 0 {
println!("[更新检查] ✓ 发现新版本: {}", version);
// 获取下载链接
// 优先使用平台特定的链接
let download_url = if let Some(ref platforms) = custom_resp.platforms {
// 检测当前平台
#[cfg(target_os = "windows")]
let platform_key = "windows-x86_64";
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
let platform_key = "darwin-x86_64";
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
let platform_key = "darwin-aarch64";
#[cfg(target_os = "linux")]
let platform_key = "linux-x86_64";
#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
let platform_key = "";
if !platform_key.is_empty() {
platforms.get(platform_key)
.map(|p| p.url.clone())
.unwrap_or_else(|| custom_resp.download_url.clone())
} else {
custom_resp.download_url.clone()
}
} else {
custom_resp.download_url.clone()
};
println!("[更新检查] 下载链接: {}", download_url);
Some(UpdateInfo {
version,
notes: custom_resp.notes,
download_url,
})
} else {
println!("[更新检查] ✗ 已是最新版本");
None
}
} else {
update_resp.download_url
// 尝试解析为 gh-info API 格式
println!("[更新检查] 尝试解析为 gh-info API 格式");
let api_resp: GhInfoApiResponse = serde_json::from_str(&response_text)
.context("解析更新 API 响应失败,既不是自定义格式也不是 gh-info 格式")?;
// 提取版本号(去掉 'v' 前缀)
let version = api_resp.latest_version.trim_start_matches('v').to_string();
println!("[更新检查] 远程版本: {}", version);
// 版本比较
let comparison = compare_version(&version, current_version);
println!("[更新检查] 版本比较结果: {} (1=有新版本, 0=相同, -1=当前更新)", comparison);
if comparison > 0 {
println!("[更新检查] ✓ 发现新版本: {}", version);
// 从 attachments 中获取下载链接
// 支持两种格式:
// 1. 字符串数组: ["URL1", "URL2", ...]
// 2. 嵌套数组: [["文件名", "URL"], ...]
let download_url = extract_download_url(&api_resp.attachments)
.ok_or_else(|| anyhow::anyhow!("未找到可下载的安装包"))?;
println!("[更新检查] 下载链接: {}", download_url);
Some(UpdateInfo {
version,
notes: api_resp.changelog,
download_url,
})
} else {
println!("[更新检查] ✗ 已是最新版本");
None
}
};
Ok(Some(UpdateInfo {
version: update_resp.version,
notes: update_resp.notes,
pub_date: update_resp.pub_date,
download_url,
signature: update_resp.signature,
}))
Ok(update_info)
}
/// 检查 GitHub Release
async fn check_github_update(repo: &str) -> Result<Option<UpdateInfo>> {
let url = format!("https://api.github.com/repos/{}/releases/latest", repo);
/// 从 attachments 中提取下载 URL
/// 支持两种格式:
/// 1. 字符串数组: ["URL1", "URL2", ...] - 优先选择 .exe 或 .msi 文件
/// 2. 嵌套数组: [["文件名", "URL"], ...] - 优先选择 .exe 或 .msi 文件
fn extract_download_url(attachments: &serde_json::Value) -> Option<String> {
// 尝试解析为字符串数组格式: ["URL1", "URL2", ...]
if let Ok(urls) = serde_json::from_value::<Vec<String>>(attachments.clone()) {
println!("[更新检查] 检测到字符串数组格式的 attachments");
// 优先选择 .exe 或 .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();
}
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(10))
.user_agent("CS工具箱/1.0")
.build()?;
let response = client.get(&url).send().await?;
if !response.status().is_success() {
return Err(anyhow::anyhow!("HTTP 状态码: {}", response.status()));
// 尝试解析为嵌套数组格式: [["文件名", "URL"], ...]
if let Ok(nested) = serde_json::from_value::<Vec<Vec<String>>>(attachments.clone()) {
println!("[更新检查] 检测到嵌套数组格式的 attachments");
// 优先选择 .exe 或 .msi 文件
if let Some(url) = nested.iter().find_map(|attachment| {
if attachment.len() >= 2 {
let filename = &attachment[0];
let url = &attachment[1];
if filename.ends_with(".exe") || filename.ends_with(".msi") {
Some(url.clone())
} else {
None
}
} else {
None
}
}) {
return Some(url);
}
// 如果没有找到 .exe 或 .msi使用第一个附件的 URL
if let Some(attachment) = nested.first() {
if attachment.len() >= 2 {
return Some(attachment[1].clone());
}
}
}
let release: GitHubRelease = response.json().await?;
// 从 tag_name 中提取版本号(移除可能的 'v' 前缀)
let version = release.tag_name.trim_start_matches('v').to_string();
// 查找适合当前平台的安装包
let download_url = find_platform_asset(&release.assets)?;
Ok(Some(UpdateInfo {
version,
notes: release.body,
pub_date: release.published_at,
download_url,
signature: None, // GitHub Release 通常不包含签名
}))
None
}
/// 查找适合当前平台的资源文件
fn find_platform_asset(assets: &[GitHubAsset]) -> Result<String> {
#[cfg(target_os = "windows")]
{
// 查找 .exe 或 .msi 或 .nsis 安装包
for asset in assets {
let name = asset.name.to_lowercase();
if name.ends_with(".exe")
|| name.ends_with(".msi")
|| (name.contains("windows") && name.contains("x86_64"))
{
return Ok(asset.browser_download_url.clone());
}
}
/// 改进的版本比较函数支持预发布版本beta.5, beta.6等)
fn compare_version(new: &str, current: &str) -> i32 {
println!("[版本比较] 比较版本: '{}' vs '{}'", new, current);
// 解析版本号:支持格式如 "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);
println!("[版本比较] 新版本 - 基础部分: {:?}, 预发布部分: {:?}", new_base, new_pre);
println!("[版本比较] 当前版本 - 基础部分: {:?}, 预发布部分: {:?}", current_base, current_pre);
// 先比较基础版本号(数字部分)
let base_comparison = compare_version_parts(&new_base, &current_base);
if base_comparison != 0 {
println!("[版本比较] 基础版本不同,返回: {}", base_comparison);
return base_comparison;
}
#[cfg(target_os = "macos")]
{
#[cfg(target_arch = "x86_64")]
{
for asset in assets {
let name = asset.name.to_lowercase();
if name.ends_with(".dmg") {
if name.contains("x86_64") || name.contains("darwin-x86_64") || (!name.contains("aarch64") && !name.contains("arm64")) {
return Ok(asset.browser_download_url.clone());
}
}
}
}
#[cfg(target_arch = "aarch64")]
{
for asset in assets {
let name = asset.name.to_lowercase();
if name.ends_with(".dmg") {
if name.contains("aarch64") || name.contains("darwin-aarch64") || name.contains("arm64") || (!name.contains("x86_64") && !name.contains("intel")) {
return Ok(asset.browser_download_url.clone());
}
}
}
}
// 如果基础版本相同(或都为空),比较预发布标识符
// 如果基础版本都为空,说明是纯预发布版本(如 beta.5 vs beta.6
let pre_comparison = compare_prerelease(&new_pre, &current_pre);
println!("[版本比较] 预发布版本比较结果: {}", pre_comparison);
// 如果基础版本都为空且预发布比较结果为0说明版本完全相同
if new_base.is_empty() && current_base.is_empty() && pre_comparison == 0 {
return 0;
}
#[cfg(target_os = "linux")]
{
for asset in assets {
let name = asset.name.to_lowercase();
if name.ends_with(".deb")
|| name.ends_with(".rpm")
|| name.ends_with(".appimage")
|| (name.contains("linux") && name.contains("x86_64"))
{
return Ok(asset.browser_download_url.clone());
}
}
}
// 如果找不到特定平台的,返回第一个资源
if let Some(asset) = assets.first() {
return Ok(asset.browser_download_url.clone());
}
Err(anyhow::anyhow!("未找到适合当前平台的安装包"))
pre_comparison
}
/// 比较版本号
/// 返回: 1 表示 version1 > version2, -1 表示 version1 < version2, 0 表示相等
fn compare_versions(version1: &str, version2: &str) -> i32 {
let v1_parts: Vec<&str> = version1
.split(|c: char| c == '.' || c == '-' || c == '_')
.collect();
let v2_parts: Vec<&str> = version2
.split(|c: char| c == '.' || c == '-' || c == '_')
/// 解析版本号,返回(基础版本号数组,预发布标识符)
fn parse_version(version: &str) -> (Vec<u32>, Option<String>) {
// 去掉 '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);
(base, Some(pre[1..].to_string())) // 跳过 '-' 字符
} else {
(version, None)
};
// 解析基础版本号(数字部分)
let base_parts: Vec<u32> = base_str
.split('.')
.filter_map(|s| s.parse().ok())
.collect();
// 如果基础版本号为空且没有预发布标识符,可能是纯预发布版本(如 "beta.5"
// 这种情况下,整个字符串作为预发布标识符
if base_parts.is_empty() && pre_str.is_none() {
// 检查是否包含非数字字符(可能是预发布版本)
if !version.chars().any(|c| c.is_ascii_digit()) {
return (vec![], Some(version.to_string()));
}
}
(base_parts, pre_str)
}
let max_len = v1_parts.len().max(v2_parts.len());
/// 比较版本号数组(数字部分)
fn compare_version_parts(new: &[u32], current: &[u32]) -> i32 {
let max_len = new.len().max(current.len());
for i in 0..max_len {
let v1_part = v1_parts.get(i).and_then(|s| s.parse::<u32>().ok()).unwrap_or(0);
let v2_part = v2_parts.get(i).and_then(|s| s.parse::<u32>().ok()).unwrap_or(0);
if v1_part > v2_part {
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 v1_part < v2_part {
} else if new_val < current_val {
return -1;
}
}
0
}
/// 下载更新文件
/// 比较预发布标识符
/// 规则:
/// - 有预发布标识符的版本 < 没有预发布标识符的版本
/// - 如果都有预发布标识符,按字典序比较
fn compare_prerelease(new: &Option<String>, current: &Option<String>) -> i32 {
match (new, current) {
// 都没有预发布标识符,版本相同
(None, None) => 0,
// 新版本有预发布,当前版本没有 -> 新版本更旧(预发布版本 < 正式版本)
(Some(_), None) => -1,
// 新版本没有预发布,当前版本有 -> 新版本更新
(None, Some(_)) => 1,
// 都有预发布标识符,按字典序比较
(Some(new_pre), Some(current_pre)) => {
// 尝试提取数字部分进行比较(如 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 {
1
} else if new_n < current_n {
-1
} else {
// 数字相同,按字符串比较
new_pre.cmp(current_pre) as i32
}
} else {
// 无法提取数字,按字符串比较
new_pre.cmp(current_pre) as i32
}
}
}
}
/// 从预发布标识符中提取数字(如 "beta.5" -> 5, "alpha.1" -> 1
fn extract_number_from_prerelease(pre: &str) -> Option<u32> {
// 尝试从最后一部分提取数字
if let Some(last_part) = pre.split('.').last() {
last_part.parse().ok()
} else {
None
}
}
/// 下载更新文件(带进度追踪和取消支持)
pub async fn download_update(
app: &tauri::AppHandle,
download_url: &str,
progress_callback: Option<Box<dyn Fn(u64, u64) + Send + Sync>>,
cancelled: Arc<AtomicBool>,
) -> Result<PathBuf> {
println!("[下载更新] 开始下载,下载链接: {}", download_url);
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(300))
.build()?;
@@ -324,6 +383,7 @@ pub async fn download_update(
return Err(anyhow::anyhow!("下载失败HTTP 状态码: {}", response.status()));
}
// 获取文件总大小
let total_size = response.content_length().unwrap_or(0);
// 获取缓存目录
@@ -344,6 +404,10 @@ pub async fn download_update(
.unwrap_or("update");
let file_path = cache_dir.join(filename);
println!("[下载更新] 文件名: {}", filename);
println!("[下载更新] 文件大小: {} bytes ({:.2} MB)", total_size, total_size as f64 / 1024.0 / 1024.0);
println!("[下载更新] 保存路径: {}", file_path.display());
// 下载文件
let mut file = fs::File::create(&file_path)?;
@@ -354,76 +418,65 @@ pub async fn download_update(
use std::io::Write;
while let Some(item) = stream.next().await {
// 检查是否取消
if cancelled.load(Ordering::Relaxed) {
// 删除部分下载的文件
let _ = fs::remove_file(&file_path);
return Err(anyhow::anyhow!("下载已取消"));
}
let chunk = item?;
file.write_all(&chunk)?;
downloaded += chunk.len() as u64;
if let Some(ref callback) = progress_callback {
callback(downloaded, total_size);
// 发送进度事件
if total_size > 0 {
let progress = (downloaded * 100) / total_size;
let _ = app.emit("update-download-progress", progress);
} else {
// 如果无法获取总大小,发送已下载的字节数
let _ = app.emit("update-download-progress", downloaded);
}
}
file.sync_all()?;
// 发送完成事件
let _ = app.emit("update-download-progress", 100u64);
println!("[下载更新] ✓ 下载完成,文件已保存到: {}", file_path.display());
Ok(file_path)
}
/// 安装更新Windows NSIS
/// 安装更新Windows
#[cfg(target_os = "windows")]
pub fn install_update(installer_path: &str) -> Result<()> {
// 使用静默安装参数
let mut cmd = Command::new(installer_path);
cmd.args(&["/S", "/D=C:\\Program Files\\CS工具箱"]);
cmd.args(&["/S"]); // 静默安装
cmd.creation_flags(CREATE_NO_WINDOW);
cmd.spawn()?;
Ok(())
}
/// 安装更新macOS
#[cfg(target_os = "macos")]
pub fn install_update(installer_path: &str) -> Result<()> {
// macOS 通常需要用户手动安装 DMG
// 这里打开安装包
Command::new("open")
.arg(installer_path)
.spawn()?;
Command::new("open").arg(installer_path).spawn()?;
Ok(())
}
/// 安装更新Linux
#[cfg(target_os = "linux")]
pub fn install_update(installer_path: &str) -> Result<()> {
// Linux 根据文件类型选择安装方式
if installer_path.ends_with(".deb") {
Command::new("sudo")
.args(&["dpkg", "-i", installer_path])
.spawn()?;
} else if installer_path.ends_with(".rpm") {
Command::new("sudo")
.args(&["rpm", "-i", installer_path])
.spawn()?;
} else if installer_path.ends_with(".AppImage") {
// AppImage 通常只需要设置执行权限
Command::new("chmod")
.args(&["+x", installer_path])
.spawn()?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compare_versions() {
assert_eq!(compare_versions("1.0.1", "1.0.0"), 1);
assert_eq!(compare_versions("1.0.0", "1.0.1"), -1);
assert_eq!(compare_versions("1.0.0", "1.0.0"), 0);
assert_eq!(compare_versions("0.0.6-beta.4", "0.0.6"), 0); // 简单比较,不考虑 beta
}
}