feat(install): 实现带进度条的游戏安装功能
- 添加进度条组件和相关状态管理 - 实现 Rust 后端解压过程的实时进度上报 - 增加安装路径中文字符校验 -优化日志显示支持时间戳开关- 引入 zip 解压库及异步运行时依赖 - 修复安装流程中的事件通信机制 - 调整 UI 布局适配新增进度条显示
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
// 只在 Windows 编译
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
// --- 1. 导入所需模块 ---
|
||||
use thiserror::Error;
|
||||
use zip::ZipArchive;
|
||||
use std::fs::{self, File};
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
use serde::Serialize;
|
||||
use tauri::Emitter;
|
||||
use tokio::time::{sleep, Duration};
|
||||
|
||||
// --- 2. (新增) 定义事件的有效负载 (Payload) ---
|
||||
// 这个结构体将作为事件发送给 Svelte
|
||||
#[derive(Clone, serde::Serialize)]
|
||||
struct InstallProgress {
|
||||
message: String,
|
||||
percentage: f32, // 0.0 到 100.0
|
||||
}
|
||||
|
||||
// --- 3. 错误定义 ---
|
||||
#[derive(Debug, Error)]
|
||||
pub enum InstallError {
|
||||
#[error("IO 错误: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
|
||||
#[error("Zip 解压错误: {0}")]
|
||||
Zip(#[from] zip::result::ZipError),
|
||||
|
||||
#[error("找不到 data.bin 文件: {0}")]
|
||||
DataFileNotFound(String),
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
impl Serialize for InstallError {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.to_string().as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[tauri::command]
|
||||
// --- 5. (修改) 添加 window 参数 ---
|
||||
pub async fn install_game(window: tauri::Window, target_dir: String) -> Result<String, InstallError> {
|
||||
|
||||
println!("Rust 后端收到命令:目标目录 {}", target_dir);
|
||||
|
||||
// --- 6. 核心改动:从文件系统读取 data.bin ---
|
||||
|
||||
// --- 6a. (新增) 发送启动事件 ---
|
||||
window.emit("install_progress", InstallProgress {
|
||||
message: "安装开始...".into(),
|
||||
percentage: 0.0
|
||||
}).ok();
|
||||
|
||||
// 1. 获取 app.exe 所在的目录
|
||||
let exe_path = std::env::current_exe()?;
|
||||
let exe_dir = exe_path.parent()
|
||||
.ok_or_else(|| InstallError::Io(io::Error::new(io::ErrorKind::NotFound, "无法获取执行文件所在目录")))?;
|
||||
|
||||
// 2. 构造 data.bin 的路径
|
||||
let data_bin_path = exe_dir.join("data.bin");
|
||||
|
||||
println!("正在查找数据文件: {:?}", data_bin_path);
|
||||
window.emit("install_progress", InstallProgress {
|
||||
message: format!("正在查找数据文件: {:?}", data_bin_path),
|
||||
percentage: 1.0 // 1%
|
||||
}).ok();
|
||||
|
||||
|
||||
if !data_bin_path.exists() {
|
||||
// 返回错误,Svelte 的 catch 块会捕获它
|
||||
return Err(InstallError::DataFileNotFound(
|
||||
format!("未在 {:?} 找到 data.bin", data_bin_path)
|
||||
));
|
||||
}
|
||||
|
||||
// 3. 从硬盘打开文件
|
||||
let archive_file = File::open(&data_bin_path)?;
|
||||
println!("成功打开 data.bin。");
|
||||
window.emit("install_progress", InstallProgress {
|
||||
message: "成功打开 data.bin。".into(),
|
||||
percentage: 5.0 // 5%
|
||||
}).ok();
|
||||
|
||||
|
||||
// 4. ZipArchive 现在读取文件
|
||||
let mut archive = ZipArchive::new(archive_file)?;
|
||||
|
||||
let total_files = archive.len() as f32;
|
||||
println!("在压缩包中找到 {} 个文件。", total_files);
|
||||
window.emit("install_progress", InstallProgress {
|
||||
message: format!("包中找到 {} 个文件。", total_files),
|
||||
percentage: 10.0 // 10%
|
||||
}).ok();
|
||||
|
||||
|
||||
// --- 7. 解压循环 (核心改动: 添加 emit) ---
|
||||
let install_path = PathBuf::from(target_dir);
|
||||
fs::create_dir_all(&install_path)?;
|
||||
|
||||
for i in 0..archive.len() {
|
||||
let mut file = archive.by_index(i)?;
|
||||
|
||||
let outpath = match file.enclosed_name() {
|
||||
Some(path) => install_path.join(path),
|
||||
None => continue,
|
||||
};
|
||||
|
||||
// --- 7a. (核心) 计算百分比并发送事件 ---
|
||||
let current_file_index = i as f32;
|
||||
let percentage = 10.0 + (current_file_index / total_files) * 85.0; // (10% + 85% = 95%)
|
||||
let message = format!("安装中 [{}/{}]: {}", i + 1, total_files as u32, file.name());
|
||||
|
||||
window.emit("install_progress", InstallProgress { message, percentage })
|
||||
.ok();
|
||||
|
||||
// --- 2. (新增) 关键修复 ---
|
||||
// 释放线程 1 毫秒,给 Svelte 留出渲染时间
|
||||
sleep(Duration::from_millis(1)).await;
|
||||
|
||||
if (&*file.name()).ends_with('/') {
|
||||
fs::create_dir_all(&outpath)?;
|
||||
} else {
|
||||
if let Some(p) = outpath.parent() {
|
||||
if !p.exists() {
|
||||
fs::create_dir_all(p)?;
|
||||
}
|
||||
}
|
||||
let mut outfile = File::create(&outpath)?;
|
||||
io::copy(&mut file, &mut outfile)?;
|
||||
}
|
||||
} // --- 循环结束 ---
|
||||
|
||||
println!("Rust 后端:解压成功。");
|
||||
window.emit("install_progress", InstallProgress {
|
||||
message: "✅ 解压完成。".into(),
|
||||
percentage: 100.0 // 100%
|
||||
}).ok();
|
||||
|
||||
Ok(format!("成功解压 {} 个文件到指定目录!", total_files as u32))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user