细节调整及优化(26.8.3)
This commit is contained in:
@@ -1,15 +1,18 @@
|
||||
//! 内核(mihomo.exe)安装 / 更新 / 版本查询。
|
||||
//! 子模块通过 `impl super::MihomoManager` 为管理器追加方法,可访问父模块私有字段。
|
||||
|
||||
use futures_util::StreamExt;
|
||||
use std::fs;
|
||||
use std::io::{Read, Write};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::Ordering;
|
||||
use tauri::{AppHandle, Emitter, Manager};
|
||||
|
||||
use crate::constants::events::KERNEL_INSTALL_PROGRESS;
|
||||
use super::{InstallProgress, KernelInfo, KernelUpdateInfo, MihomoManager};
|
||||
|
||||
/// 用户主动取消下载的标记错误信息(前端据此静默处理,不弹错误 toast)
|
||||
const KERNEL_CANCELLED: &str = "下载已取消";
|
||||
|
||||
impl MihomoManager {
|
||||
// ---------- 内核 ----------
|
||||
pub fn kernel_info(&self) -> KernelInfo {
|
||||
@@ -203,57 +206,15 @@ impl MihomoManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// 下载并安装内核(首次安装与更新共用此方法)
|
||||
/// - mirror_prefix: 用户选择的镜像源前缀(空串=直连 GitHub)
|
||||
/// - 流式下载:实时推送下载进度到前端
|
||||
/// - zip crate 解压:替代 PowerShell,避免执行策略问题
|
||||
/// - 备份旧内核:替换前备份为 .bak
|
||||
/// 任何阶段失败都会 emit error 事件,避免前端进度卡在初始状态
|
||||
pub async fn install_kernel(&self, app: &AppHandle, mirror_prefix: String) -> Result<KernelInfo, String> {
|
||||
let result = self.install_kernel_inner(app, mirror_prefix).await;
|
||||
/// 应用内核更新:下载阶段已由下载模块完成,本方法仅做 need_stop → 解压 → 替换。
|
||||
/// zip_path: 下载模块下载完成的 zip 文件路径。
|
||||
/// 任何阶段失败都会 emit error 事件,避免前端进度卡住。
|
||||
pub async fn apply_kernel_update(&self, app: &AppHandle, zip_path: PathBuf) -> Result<KernelInfo, String> {
|
||||
self.kernel_cancel.store(false, Ordering::SeqCst);
|
||||
let _ = self.kernel_cancel_tx.send(false);
|
||||
let result = self.apply_kernel_inner(app, zip_path).await;
|
||||
if let Err(ref e) = result {
|
||||
let _ = app.emit(
|
||||
KERNEL_INSTALL_PROGRESS,
|
||||
InstallProgress {
|
||||
stage: "error".into(),
|
||||
percent: 0,
|
||||
downloaded_bytes: 0,
|
||||
total_bytes: None,
|
||||
message: e.clone(),
|
||||
},
|
||||
);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
async fn install_kernel_inner(&self, app: &AppHandle, mirror_prefix: String) -> Result<KernelInfo, String> {
|
||||
let info = self.check_kernel_update().await?;
|
||||
let zip_path = self.cores_dir().join("mihomo-update.zip");
|
||||
let extract_dir = self.cores_dir().join("mihomo-update-tmp");
|
||||
|
||||
// 拼接用户选择的镜像源 URL
|
||||
let url = if mirror_prefix.is_empty() {
|
||||
info.download_url.clone()
|
||||
} else {
|
||||
format!("{}{}", mirror_prefix, info.download_url)
|
||||
};
|
||||
let label = if mirror_prefix.is_empty() { "GitHub 直连".to_string() } else { mirror_prefix.clone() };
|
||||
let _ = app.emit(
|
||||
KERNEL_INSTALL_PROGRESS,
|
||||
InstallProgress {
|
||||
stage: "downloading".into(),
|
||||
percent: 0,
|
||||
downloaded_bytes: 0,
|
||||
total_bytes: None,
|
||||
message: format!("正在下载:{}", label),
|
||||
},
|
||||
);
|
||||
|
||||
// 单源下载(用户已选择)
|
||||
match self.download_with_progress(app, &url, &zip_path).await {
|
||||
Ok(()) => {}
|
||||
Err(e) => {
|
||||
let msg = format!("下载失败({}):{}", label, e);
|
||||
if e != KERNEL_CANCELLED {
|
||||
let _ = app.emit(
|
||||
KERNEL_INSTALL_PROGRESS,
|
||||
InstallProgress {
|
||||
@@ -261,11 +222,50 @@ impl MihomoManager {
|
||||
percent: 0,
|
||||
downloaded_bytes: 0,
|
||||
total_bytes: None,
|
||||
message: msg.clone(),
|
||||
message: e.clone(),
|
||||
},
|
||||
);
|
||||
let _ = fs::remove_file(&zip_path);
|
||||
return Err(msg);
|
||||
}
|
||||
}
|
||||
self.kernel_cancel.store(false, Ordering::SeqCst);
|
||||
let _ = self.kernel_cancel_tx.send(false);
|
||||
result
|
||||
}
|
||||
|
||||
async fn apply_kernel_inner(&self, app: &AppHandle, zip_path: PathBuf) -> Result<KernelInfo, String> {
|
||||
let extract_dir = self.cores_dir().join("mihomo-update-tmp");
|
||||
|
||||
// 检查 zip 文件是否存在
|
||||
if !zip_path.exists() {
|
||||
return Err(format!("下载文件不存在: {}", zip_path.display()));
|
||||
}
|
||||
|
||||
// 解压替换前需要等待前端确认 mihomo 已停止(否则 exe 文件被占用)
|
||||
if self.kernel_cancel.load(Ordering::SeqCst) {
|
||||
return Err(KERNEL_CANCELLED.to_string());
|
||||
}
|
||||
let _ = app.emit(
|
||||
KERNEL_INSTALL_PROGRESS,
|
||||
InstallProgress {
|
||||
stage: "need_stop".into(),
|
||||
percent: 90,
|
||||
downloaded_bytes: 0,
|
||||
total_bytes: None,
|
||||
message: "需要停止 mihomo 才能继续安装".into(),
|
||||
},
|
||||
);
|
||||
// 创建 oneshot 通道等待前端确认
|
||||
let (tx, mut rx) = tokio::sync::oneshot::channel::<()>();
|
||||
*self.install_confirm.lock().unwrap() = Some(tx);
|
||||
let mut cancel_rx = self.kernel_cancel_tx.subscribe();
|
||||
loop {
|
||||
if self.kernel_cancel.load(Ordering::SeqCst) {
|
||||
*self.install_confirm.lock().unwrap() = None;
|
||||
return Err(KERNEL_CANCELLED.to_string());
|
||||
}
|
||||
tokio::select! {
|
||||
_ = &mut rx => break,
|
||||
_ = cancel_rx.changed() => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,8 +299,6 @@ impl MihomoManager {
|
||||
}
|
||||
|
||||
// 在解压目录中递归查找 exe 文件
|
||||
// mihomo zip 内的 exe 名字通常与 zip 同名(如 mihomo-windows-amd64-v3-v1.19.13.exe),
|
||||
// 不是固定的 mihomo.exe,所以查找唯一的 .exe 文件即可
|
||||
let new_exe = self
|
||||
.find_exe_in_dir(&extract_dir)
|
||||
.ok_or_else(|| "解压后未找到任何 .exe 文件".to_string())?;
|
||||
@@ -313,7 +311,7 @@ impl MihomoManager {
|
||||
percent: 96,
|
||||
downloaded_bytes: 0,
|
||||
total_bytes: None,
|
||||
message: "正在安装...".into(),
|
||||
message: "正在替换内核...".into(),
|
||||
},
|
||||
);
|
||||
let kernel = self.kernel_path();
|
||||
@@ -345,57 +343,6 @@ impl MihomoManager {
|
||||
Ok(final_info)
|
||||
}
|
||||
|
||||
/// 流式下载并实时推送进度事件
|
||||
async fn download_with_progress(
|
||||
&self,
|
||||
app: &AppHandle,
|
||||
url: &str,
|
||||
dest: &PathBuf,
|
||||
) -> Result<(), String> {
|
||||
let resp = self
|
||||
.client
|
||||
.get(url)
|
||||
.header("User-Agent", "thing-app")
|
||||
.timeout(std::time::Duration::from_secs(30))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("请求失败: {}", e))?;
|
||||
if !resp.status().is_success() {
|
||||
return Err(format!("HTTP {}", resp.status()));
|
||||
}
|
||||
let total = resp.content_length();
|
||||
let mut stream = resp.bytes_stream();
|
||||
let mut file = fs::File::create(dest).map_err(|e| format!("创建文件失败: {}", e))?;
|
||||
let mut downloaded: u64 = 0;
|
||||
let mut last_percent: u8 = 0;
|
||||
while let Some(chunk) = stream.next().await {
|
||||
let chunk = chunk.map_err(|e| format!("读取流失败: {}", e))?;
|
||||
file.write_all(&chunk).map_err(|e| format!("写入文件失败: {}", e))?;
|
||||
downloaded += chunk.len() as u64;
|
||||
// 下载占总进度的 0-90%
|
||||
let percent = match total {
|
||||
Some(t) if t > 0 => ((downloaded as f64 / t as f64) * 90.0) as u8,
|
||||
_ => 0,
|
||||
};
|
||||
// 仅在变化超过 1% 时 emit,避免事件轰炸
|
||||
if percent >= last_percent + 1 {
|
||||
last_percent = percent;
|
||||
let _ = app.emit(
|
||||
KERNEL_INSTALL_PROGRESS,
|
||||
InstallProgress {
|
||||
stage: "downloading".into(),
|
||||
percent,
|
||||
downloaded_bytes: downloaded,
|
||||
total_bytes: total,
|
||||
message: format!("已下载 {:.2} MB", downloaded as f64 / 1024.0 / 1024.0),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
file.flush().map_err(|e| format!("flush 失败: {}", e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 用 zip crate 解压(纯 Rust,避免 PowerShell 执行策略问题)
|
||||
fn extract_zip(&self, zip_path: &PathBuf, dest: &PathBuf) -> Result<(), String> {
|
||||
let file = fs::File::open(zip_path).map_err(|e| format!("打开 zip 失败: {}", e))?;
|
||||
|
||||
Reference in New Issue
Block a user