use rand::Rng; use serde::{Deserialize, Serialize}; use std::io::Read; use std::fs::{self, File}; use std::io::Write as IoWrite; use std::path::PathBuf; use std::pin::Pin; use std::process::Command; use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64}; use cube_lib::websocket::{WebSocketClient, WebSocketConfig}; // ===================== 文件下载状态 ===================== struct DownloadState { filename: String, offset: u64, temp_path: Option, file: Option, } impl Default for DownloadState { fn default() -> Self { Self { filename: String::new(), offset: 0, temp_path: None, file: None, } } } static DOWNLOAD_STATE: std::sync::Mutex = std::sync::Mutex::new(DownloadState { filename: String::new(), offset: 0, temp_path: None, file: None, }); // ===================== 下载流程状态 ===================== static BOOTLOADER_DOWNLOADED: std::sync::Mutex = std::sync::Mutex::new(false); static UPDATER_DOWNLOADED: std::sync::Mutex = std::sync::Mutex::new(false); static SERVER_UPDATER_VERSION: std::sync::Mutex> = std::sync::Mutex::new(None); /// 标记一次更新检查是否已完成(用于主循环控制) static UPDATE_CHECK_DONE: std::sync::Mutex = std::sync::Mutex::new(false); /// 标记本次运行是否执行了更新(下载了文件) static UPDATE_PERFORMED: std::sync::Mutex = std::sync::Mutex::new(false); // ===================== 版本比较与下载 ===================== /// 获取 Updater 数据目录(X:\AppData\,存放 BootLoader.exe 等) fn get_updater_data_dir() -> PathBuf { let exe_path = std::env::current_exe().expect("Failed to get executable path"); let drive = exe_path .parent() .and_then(|p| p.as_os_str().to_str()) .and_then(|s| s.split('\\').next()) .unwrap_or("C:"); let appdata = PathBuf::from(format!("{}/AppData", drive)); let _ = fs::create_dir_all(&appdata); appdata } /// 获取本地文件版本号(使用 PowerShell 获取 PE 文件版本信息) fn get_local_file_version(filename: &str) -> String { let file_path = get_updater_data_dir().join(filename); if !file_path.exists() { return "0.0.0".to_string(); } #[cfg(windows)] { let path_str = file_path.to_string_lossy().to_string(); let ps_script = format!( "(Get-Item -LiteralPath '{}' -ErrorAction SilentlyContinue).VersionInfo.FileVersion", path_str.replace("'", "''") ); let output = Command::new("powershell") .args(["-NoProfile", "-NonInteractive", "-Command", &ps_script]) .output(); if let Ok(output) = output { let stdout = String::from_utf8_lossy(&output.stdout); let version = stdout.trim(); if !version.is_empty() && version != "0" && !version.contains("没有版本") { return version.to_string(); } } // 备用:使用文件修改时间作为"版本" if let Ok(metadata) = fs::metadata(&file_path) { use std::time::UNIX_EPOCH; if let Ok(modified) = metadata.modified() { let duration = modified.duration_since(UNIX_EPOCH).unwrap_or_default(); return format!("{}.{}", duration.as_secs(), duration.subsec_nanos() / 1_000_000); } } "0.0.0".to_string() } #[cfg(not(windows))] { "0.0.0".to_string() } } /// 比较两个版本号(a < b 返回 true) /// 使用 CubeLib 库的版本比较函数 fn version_less_than(a: &str, b: &str) -> bool { cube_lib::version_less_than(a, b) } /// 计算本地文件前 N 字节的 MD5 hash(字节数为 0 表示全部) fn compute_file_hash(filename: &str, bytes: u64, _debug: bool) -> Option { let file_path = get_updater_data_dir().join(filename); if !file_path.exists() { return None; } let mut file = File::open(&file_path).ok()?; let file_size = file.metadata().ok()?.len(); // 如果 bytes=0 或超过文件大小,则计算整个文件 let read_bytes = if bytes == 0 || bytes > file_size { file_size } else { bytes }; // 读取前 read_bytes 字节 let mut buffer = vec![0u8; read_bytes as usize]; file.read_exact(&mut buffer).ok()?; let hash = md5::compute(&buffer); Some(format!("{:x}", hash)) } /// 获取临时文件的当前大小(字节数) /// 注意:Updater.exe 的临时文件是 Updater.new.exe.tmp fn get_tmp_file_size(filename: &str) -> u64 { let tmp_filename = if filename == "Updater.exe" { "Updater.new.exe.tmp" } else { filename }; let tmp_path = get_updater_data_dir().join(format!("{}.tmp", tmp_filename)); tmp_path.metadata().map(|m| m.len()).unwrap_or(0) } /// 检查是否存在 Updater.new.exe 文件 fn has_updater_new_exe() -> bool { let updater_new_path = get_updater_data_dir().join("Updater.new.exe"); updater_new_path.exists() } /// 发送获取 Updater.exe 版本的请求 fn request_updater_version(sender: &cube_lib::websocket::MessageSender, device_number: &str, debug: bool) { let msg_str = format!( r#"{{"Type":"GetFileVer","Data":{{"DeviceNumber":"{}","file_list":["Updater.exe"]}}}}"#, device_number ); if debug { let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); println!("{} [版本] 请求 Updater.exe 版本: {}", ts, msg_str); } sender.send(msg_str); } /// 检查并下载 Updater.exe /// 当 BootLoader 下载完成后调用此函数 /// 返回 true 表示需要继续等待下载,false 表示流程结束 fn check_and_download_updater(sender: &cube_lib::websocket::MessageSender, device_number: &str, debug: bool) -> bool { let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); // 获取服务端 Updater.exe 版本号 let server_version = SERVER_UPDATER_VERSION.lock().unwrap().clone(); if let Some(sv) = server_version { let local_version = get_local_file_version("Updater.exe"); if debug { println!("{} [版本] Updater.exe: 服务端={}, 本地={}", ts, sv, local_version); } // 比较版本:需要下载的条件 let need_update = local_version == "0.0.0" || version_less_than(&local_version, &sv); if need_update { // 需要下载 Updater.exe let tmp_size = get_tmp_file_size("Updater.exe"); if tmp_size > 0 { if debug { println!("{} [续传] Updater.exe 发现未完成下载,请求 hash 校验...", ts); } request_file_md5(sender, "Updater.exe", tmp_size, debug); } else { if debug { println!("{} [升级] Updater.exe 需要更新,开始下载...", ts); } request_download(sender, "Updater.exe", 0, debug); } true // 需要继续等待下载 } else { // 版本已是最新,检查是否有 Updater.new.exe if debug { println!("{} [版本] Updater.exe 版本已是最新,无需下载", ts); } // 删除可能存在的旧 Updater.new.exe if has_updater_new_exe() { let updater_new_path = get_updater_data_dir().join("Updater.new.exe"); let _ = fs::remove_file(&updater_new_path); if debug { println!("{} [清理] 删除旧的 Updater.new.exe", ts); } } // 返回 false 表示 Updater 不需要更新,调用者负责输出日志并退出 false } } else { // 没有收到服务端 Updater 版本 → 重新请求 Updater 版本 if debug { println!("{} [警告] 未收到 Updater.exe 版本信息,重新请求...", ts); } request_updater_version(sender, device_number, debug); true // 需要继续等待版本响应 } } /// 安排启动 BootLoader.exe(延迟执行,等待所有下载完成) /// 通过 shutdown_tx 发送断连信号,通知主循环优雅退出 fn schedule_bootloader_launch( debug: bool, shutdown_tx_arc: std::sync::Arc>>>, ) { // 延迟 1 秒后启动,确保文件句柄已关闭 std::thread::spawn(move || { std::thread::sleep(std::time::Duration::from_secs(1)); let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); let bootloader_path = get_updater_data_dir().join("BootLoader.exe"); if !bootloader_path.exists() { if debug { println!("{} [错误] BootLoader.exe 不存在,无法启动", ts); } return; } // 启动 BootLoader.exe if debug { println!("{} [启动] 正在启动 BootLoader.exe...", ts); } #[cfg(windows)] { use std::os::windows::process::CommandExt; const DETACHED_PROCESS: u32 = 0x00000008; const CREATE_NO_WINDOW: u32 = 0x08000000; match Command::new(&bootloader_path) .args(["--from-updater"]) .creation_flags(DETACHED_PROCESS | CREATE_NO_WINDOW) .spawn() { Ok(_) => { if debug { println!("{} [启动] BootLoader.exe 已启动,Updater 即将退出", ts); } // 发送断连信号,通知主循环优雅退出 if let Some(tx) = shutdown_tx_arc.lock().unwrap().take() { let _ = tx.send(()); } } Err(e) => { eprintln!("{} [错误] 启动 BootLoader.exe 失败: {}", ts, e); // 启动失败也发送断连信号 if let Some(tx) = shutdown_tx_arc.lock().unwrap().take() { let _ = tx.send(()); } } } } #[cfg(not(windows))] { match Command::new(&bootloader_path) .arg("--from-updater") .spawn() { Ok(_) => { if debug { println!("{} [启动] BootLoader.exe 已启动,Updater 即将退出", ts); } if let Some(tx) = shutdown_tx_arc.lock().unwrap().take() { let _ = tx.send(()); } } Err(e) => { eprintln!("{} [错误] 启动 BootLoader.exe 失败: {}", ts, e); if let Some(tx) = shutdown_tx_arc.lock().unwrap().take() { let _ = tx.send(()); } } } } }); } /// 发送 GetFileMd5 请求 fn request_file_md5(sender: &cube_lib::websocket::MessageSender, filename: &str, bytes: u64, debug: bool) { let msg_str = format!( r#"{{"Type":"GetFileMd5","Data":{{"filename":"{}","bytes":{}}}}}"#, filename, bytes ); if debug { let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); println!("{} 发送消息:{}", ts, msg_str); } sender.send(msg_str); } /// 发送文件下载请求(断点续传) fn request_download(sender: &cube_lib::websocket::MessageSender, filename: &str, offset: u64, debug: bool) { let msg_str = format!( r#"{{"Type":"DownloadFile","Data":{{"filename":"{}","offset":{}}}}}"#, filename, offset ); if debug { let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); println!("{} 发送消息:{}", ts, msg_str); } sender.send(msg_str); } /// 处理收到的文件块 fn handle_file_chunk(data: &serde_json::Map, debug: bool) -> Option { let filename = data.get("filename").and_then(|v| v.as_str()).unwrap_or(""); let offset = data.get("offset").and_then(|v| v.as_u64()).unwrap_or(0); let chunk_data = data.get("data").and_then(|v| v.as_str()).unwrap_or(""); let is_last = data.get("is_last").and_then(|v| v.as_bool()).unwrap_or(false); // Updater.exe 保存为 Updater.new.exe let final_filename = if filename == "Updater.exe" { "Updater.new.exe".to_string() } else { filename.to_string() }; let mut state = DOWNLOAD_STATE.lock().unwrap(); // 如果是新文件或 offset=0,重置状态 if state.filename != final_filename || offset == 0 { // 关闭旧文件 if let Some(f) = state.file.take() { drop(f); } // 删除旧临时文件 if let Some(ref tp) = state.temp_path { let _ = fs::remove_file(tp); } // 使用 AppData/Updater/ 作为数据目录 let data_dir = get_updater_data_dir(); let temp_path = data_dir.join(format!("{}.tmp", final_filename)); // 创建临时文件(截断) match File::create(&temp_path) { Ok(file) => { state.filename = final_filename.clone(); state.offset = 0; state.temp_path = Some(temp_path); state.file = Some(file); } Err(_) => { if debug { eprintln!("[下载] 无法创建临时文件: {}", filename); } return None; } } } // 解码并追加数据 let current_offset = state.offset; if offset == current_offset { if let Some(ref mut file) = state.file { match BASE64.decode(chunk_data) { Ok(decoded) => { match file.write_all(&decoded) { Ok(_) => { let _ = file.flush(); state.offset += decoded.len() as u64; } Err(e) => { if debug { eprintln!("[下载] 写入失败: {}", e); } } } } Err(e) => { if debug { eprintln!("[下载] Base64 解码失败: {}", e); } } } } } else { if debug { eprintln!("[下载] 偏移不匹配: 期望 {}, 收到 {}", current_offset, offset); } } // 最后一块:重命名临时文件为正式文件 if is_last { let temp_path = state.temp_path.clone(); let final_offset = state.offset; // 关闭文件句柄 if let Some(f) = state.file.take() { drop(f); } if let Some(ref temp) = temp_path { let data_dir = get_updater_data_dir(); let final_path = data_dir.join(&final_filename); // 原子重命名 if let Err(e) = fs::rename(temp, &final_path) { if debug { eprintln!("[下载] 重命名失败: {}", e); } // 尝试直接覆盖写入 let _ = fs::copy(temp, &final_path); let _ = fs::remove_file(temp); } if debug { let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); println!("{} [下载] {} 下载完成,共 {} 字节", ts, final_filename, final_offset); } } // 重置状态 state.filename = String::new(); state.offset = 0; state.temp_path = None; state.file = None; // 返回下载完成的文件名,用于后续流程判断 Some(final_filename) } else { None } } /// 处理下载完成消息 fn handle_download_complete(data: &serde_json::Map, debug: bool) -> Option { let filename = data.get("filename").and_then(|v| v.as_str()).unwrap_or(""); let size = data.get("size").and_then(|v| v.as_u64()).unwrap_or(0); // Updater.exe 保存为 Updater.new.exe let final_filename = if filename == "Updater.exe" { "Updater.new.exe".to_string() } else { filename.to_string() }; if debug { let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); println!("{} [下载] {} 下载完成,最终大小: {} 字节", ts, final_filename, size); } let mut state = DOWNLOAD_STATE.lock().unwrap(); // 如果有临时文件,执行重命名(防止 DownloadComplete 先于最后一个 FileChunk 到达) // 先克隆需要的数据 let temp_to_rename = state.temp_path.clone(); let has_temp = temp_to_rename.is_some(); if has_temp { let data_dir = get_updater_data_dir(); let final_path = data_dir.join(&final_filename); // 先关闭文件句柄并获取 temp_path let temp_path_owned = state.temp_path.take().unwrap(); state.filename = String::new(); state.offset = 0; // 原子重命名 if let Err(e) = fs::rename(&temp_path_owned, &final_path) { if debug { eprintln!("[下载] 重命名失败: {}", e); } // 尝试直接覆盖写入 let _ = fs::copy(&temp_path_owned, &final_path); let _ = fs::remove_file(&temp_path_owned); } if debug { let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); println!("{} [下载] {} 临时文件已重命名", ts, final_filename); } } else { // 没有临时文件,只重置状态 state.filename = String::new(); state.offset = 0; } Some(final_filename) } /// Updater 自身配置(AppData/Updater/config.json) /// 只负责 Updater 自己的行为参数,连接地址从公共 config.json 加载 #[derive(Debug, Serialize, Deserialize)] struct Config { /// 调试模式:true 时保留控制台窗口并输出日志 debug_mode: bool, } impl Default for Config { fn default() -> Self { Self { debug_mode: false } } } /// 获取 Updater 自身配置路径 AppData/Updater/config.json fn get_updater_config_path() -> PathBuf { get_updater_data_dir().join("Updater").join("config.json") } /// 获取公共配置路径 AppData/config.json(与 BootLoader 同级) fn get_public_config_path() -> PathBuf { let exe_path = std::env::current_exe().expect("Failed to get executable path"); let drive = exe_path .parent() .and_then(|p| p.as_os_str().to_str()) .and_then(|s| s.split('\\').next()) .unwrap_or("C:"); PathBuf::from(format!("{}/AppData/config.json", drive)) } /// 加载 Updater 自身配置;若文件不存在则写入默认值 fn load_updater_config() -> Config { let config_path = get_updater_config_path(); if config_path.exists() { if let Ok(content) = fs::read_to_string(&config_path) { if let Ok(config) = serde_json::from_str::(&content) { return config; } } } // 文件不存在或解析失败 → 写入默认值 let default_config = Config::default(); if let Ok(content) = serde_json::to_string_pretty(&default_config) { let _ = fs::write(&config_path, content); } default_config } /// 从公共 config.json 读取 ServerUrl 字段 fn resolve_ws_url() -> String { let config_path = get_public_config_path(); if let Ok(content) = fs::read_to_string(&config_path) { if let Ok(json) = serde_json::from_str::(&content) { if let Some(url) = json.get("ServerUrl").and_then(|v| v.as_str()) { return url.to_string(); } } } // 读取失败 → 降级到默认值 "ws://127.0.0.1:8087/ws".to_string() } /// 从公共 config.json 读取 DeviceNumber 字段 fn resolve_device_number() -> String { let config_path = get_public_config_path(); if let Ok(content) = fs::read_to_string(&config_path) { if let Ok(json) = serde_json::from_str::(&content) { // 尝试多个可能的字段名 if let Some(id) = json.get("DeviceNumber").and_then(|v| v.as_str()) { if !id.is_empty() { return id.to_string(); } } if let Some(id) = json.get("StationId").and_then(|v| v.as_str()) { if !id.is_empty() { return id.to_string(); } } if let Some(id) = json.get("Station").and_then(|v| v.as_str()) { if !id.is_empty() { return id.to_string(); } } } } // 读取失败 → 降级到默认值 "UNKNOWN".to_string() } fn is_process_running(process_name: &str) -> bool { use std::process::id; let current_pid = id().to_string(); let output = Command::new("tasklist") .args(["/FI", &format!("IMAGENAME eq {}", process_name), "/FO", "CSV"]) .output() .expect("Failed to execute tasklist"); let output_str = String::from_utf8_lossy(&output.stdout); let lines: Vec<&str> = output_str.lines().collect(); let mut count = 0; for line in lines { if line.contains(&format!("\"{}\"", process_name)) && !line.contains(¤t_pid) { count += 1; } } count > 0 } /// 主动断连信号(用于在消息回调中请求优雅退出) /// - shutdown_tx: 从同步回调中发送,通知主循环主动断开 /// - disconnect_rx: 在主循环中等待 fn make_shutdown_channel() -> ( std::sync::Arc>>>, tokio::sync::oneshot::Receiver<()>, ) { let (tx, rx) = tokio::sync::oneshot::channel(); (std::sync::Arc::new(std::sync::Mutex::new(Some(tx))), rx) } /// 运行 Updater(使用 CubeLib 内置的自动重连) /// 返回本次运行是否执行了更新(下载了文件) async fn run_updater(debug_mode: bool) -> bool { // 重置本次运行的更新状态 *UPDATE_PERFORMED.lock().unwrap() = false; *BOOTLOADER_DOWNLOADED.lock().unwrap() = false; *UPDATER_DOWNLOADED.lock().unwrap() = false; *SERVER_UPDATER_VERSION.lock().unwrap() = None; // 主动断连通道 let (shutdown_tx_arc, mut disconnect_rx) = make_shutdown_channel(); // 加载初始 URL let server_url = resolve_ws_url(); if debug_mode { println!("========================================"); println!("Updater 启动 (调试模式)"); println!("服务器地址: {}", server_url); println!("自动重连: 启用 (指数退避: 1s - 30s)"); println!("========================================"); } // 创建 WebSocket 配置(启用自动重连) let config = WebSocketConfig::new(&server_url) .with_client_type("Updater") .with_debug(debug_mode) .with_reconnect(true) // 启用自动重连 .with_reconnect_delay(1000) // 初始延迟 1s .with_max_reconnect_delay(30000); // 最大延迟 30s // 创建 WebSocket 客户端 let mut client = WebSocketClient::new(config); // 设置连接成功回调 let debug_connected = debug_mode; client.on_connected(move |url| { if debug_connected { let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); println!("{} 收到消息:{}", ts, url); } }); // 设置消息接收回调 let debug_msg = debug_mode; let device_number = resolve_device_number(); let shutdown_tx_arc_clone = shutdown_tx_arc.clone(); client.on_message(move |msg_type, data, sender| { let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); if debug_msg { // 收到消息日志:data 是完整 parsed 对象,取其中的 Data 字段打印 let actual_data = data.get("Data").unwrap_or(&serde_json::Value::Null); let data_str = serde_json::to_string(actual_data).unwrap_or_else(|_| "{}".to_string()); println!("{} 收到消息:{{\"Type\":{},\"Data\":{}}}", ts, serde_json::to_string(&msg_type).unwrap_or_default(), data_str ); } // 收到 welcome 后,发送 GetFileVer(同时请求 BootLoader 和 Updater) if msg_type == "welcome" { if !device_number.is_empty() && device_number != "UNKNOWN" { let msg_str = format!( r#"{{"Type":"GetFileVer","Data":{{"DeviceNumber":"{}","file_list":["BootLoader.exe","Updater.exe"]}}}}"#, device_number ); if debug_msg { let ts2 = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); println!("{} 发送消息:{}", ts2, msg_str); } sender.send(msg_str); } } // 处理 FileVer 响应 if msg_type == "FileVer" { if let Some(file_versions) = data.get("Data").and_then(|d| d.get("file_versions")).and_then(|v| v.as_object()) { // 第一步:先遍历保存所有版本号 for (filename, server_ver) in file_versions { let server_version = server_ver.as_str().unwrap_or("0.0.0"); // 保存 Updater.exe 的服务端版本号 if filename == "Updater.exe" { *SERVER_UPDATER_VERSION.lock().unwrap() = Some(server_version.to_string()); } } // 第二步:再遍历处理每个文件 for (filename, server_ver) in file_versions { let server_version = server_ver.as_str().unwrap_or("0.0.0"); let local_version = get_local_file_version(filename); let tmp_size = get_tmp_file_size(filename); if debug_msg { let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); println!("{} [版本] {}: 服务端={}, 本地={}, tmp大小={}", ts, filename, server_version, local_version, tmp_size); } // 比较版本:如果本地不存在或比服务端旧,则下载 let need_update = local_version == "0.0.0" || version_less_than(&local_version, server_version); if need_update { if tmp_size > 0 { // 有临时文件,请求 hash 校验 if debug_msg { let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); println!("{} [续传] {} 发现未完成下载,请求 hash 校验...", ts, filename); } request_file_md5(&sender, filename, tmp_size, debug_msg); *UPDATE_PERFORMED.lock().unwrap() = true; } else { // 无临时文件,从头下载 if debug_msg { let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); println!("{} [升级] {} 需要更新,开始下载...", ts, filename); } request_download(&sender, filename, 0, debug_msg); *UPDATE_PERFORMED.lock().unwrap() = true; } } else { // 不需要更新 if debug_msg { let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); println!("{} [版本] {} 版本已是最新,无需更新", ts, filename); } // 对于 Updater.exe,检查是否有 Updater.new.exe if filename == "Updater.exe" { if has_updater_new_exe() { // 删除旧版 Updater.new.exe(因为会重新下载) let updater_new_path = get_updater_data_dir().join("Updater.new.exe"); let _ = fs::remove_file(&updater_new_path); if debug_msg { let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); println!("{} [清理] 删除旧的 Updater.new.exe", ts); } } } // 标记 BootLoader 已"下载完成"(无需更新) if filename == "BootLoader.exe" { *BOOTLOADER_DOWNLOADED.lock().unwrap() = true; if debug_msg { let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); println!("{} [升级] BootLoader.exe 版本已是最新", ts); } // BootLoader 无需下载,检查 Updater if !check_and_download_updater(&sender, &device_number, debug_msg) { // Updater 也不需要更新 → 检查是否有正在下载的临时文件 let tmp_size = get_tmp_file_size("Updater.exe"); if tmp_size > 0 { // 有正在下载的 Updater.tmp,等待下载完成 if debug_msg { let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); println!("{} [升级] 发现未完成下载,等待 Updater.exe 下载完成...", ts); } // 请求 hash 校验,继续等待 request_file_md5(&sender, "Updater.exe", tmp_size, debug_msg); } else { // 真的不需要更新,也没有临时文件 → 标记检查完成 *UPDATE_CHECK_DONE.lock().unwrap() = true; if debug_msg { let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); println!("{} [升级] 所有文件已是最新版本,等待下次检查...", ts); } } } } } } } } // 处理 Md5 响应 if msg_type == "Md5" { if let Some(md5_data) = data.get("Data").and_then(|v| v.as_object()) { let filename = md5_data.get("filename").and_then(|v| v.as_str()).unwrap_or(""); let server_md5 = md5_data.get("md5").and_then(|v| v.as_str()).unwrap_or(""); let bytes = md5_data.get("bytes").and_then(|v| v.as_u64()).unwrap_or(0); // Updater.exe 的临时文件名是 Updater.new.exe.tmp let tmp_filename = if filename == "Updater.exe" { "Updater.new.exe.tmp".to_string() } else { format!("{}.tmp", filename) }; // 计算本地 tmp 文件前 bytes 字节的 md5 let local_md5 = compute_file_hash(&tmp_filename, bytes, debug_msg); if debug_msg { let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); if let Some(ref lm) = local_md5 { println!("{} [续传] {} md5对比: 本地={}, 服务端={}", ts, filename, lm, server_md5); } else { println!("{} [续传] {} 无法计算本地md5,重新下载", ts, filename); } } // 比较 md5 if local_md5.as_deref() == Some(server_md5) { // md5 相同,从 offset bytes 处续传 if debug_msg { let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); println!("{} [续传] {} md5匹配,从 {} 字节处续传", ts, filename, bytes); } request_download(&sender, filename, bytes, debug_msg); } else { // md5 不同,删除临时文件,重新下载 if debug_msg { let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); println!("{} [续传] {} md5不匹配,重新下载", ts, filename); } let tmp_path = get_updater_data_dir().join(&tmp_filename); let _ = fs::remove_file(&tmp_path); request_download(&sender, filename, 0, debug_msg); } } } // 处理文件块 if msg_type == "FileChunk" { if let Some(data_obj) = data.get("Data").and_then(|v| v.as_object()) { let completed = handle_file_chunk(data_obj, debug_msg); // 只处理 Updater 下载完成(BootLoader 由 DownloadComplete 统一处理,避免重复) if let Some(ref completed_file) = completed { if completed_file == "Updater.new.exe" { let already_done = *UPDATER_DOWNLOADED.lock().unwrap(); if !already_done { *UPDATER_DOWNLOADED.lock().unwrap() = true; if debug_msg { let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); println!("{} [升级] Updater.new.exe 下载完成(FileChunk),准备启动 BootLoader...", ts); } schedule_bootloader_launch(debug_msg, shutdown_tx_arc_clone.clone()); } } } } } // 处理下载完成(统一处理,避免 FileChunk 和 DownloadComplete 重复处理) if msg_type == "DownloadComplete" { if let Some(data_obj) = data.get("Data").and_then(|v| v.as_object()) { let completed = handle_download_complete(data_obj, debug_msg); if let Some(ref completed_file) = completed { // BootLoader.exe 下载完成 if completed_file == "BootLoader.exe" { let already_started = *BOOTLOADER_DOWNLOADED.lock().unwrap(); if !already_started { *BOOTLOADER_DOWNLOADED.lock().unwrap() = true; if debug_msg { let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); println!("{} [升级] BootLoader.exe 下载完成,开始下载 Updater.exe...", ts); } // 开始下载 Updater.exe request_download(&sender, "Updater.exe", 0, debug_msg); } } // Updater.new.exe 下载完成 else if completed_file == "Updater.new.exe" { let already_done = *UPDATER_DOWNLOADED.lock().unwrap(); if !already_done { *UPDATER_DOWNLOADED.lock().unwrap() = true; if debug_msg { let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); println!("{} [升级] Updater.new.exe 下载完成(DownloadComplete),准备启动 BootLoader...", ts); } schedule_bootloader_launch(debug_msg, shutdown_tx_arc.clone()); } } } } } }); // 设置断开连接回调 let debug_disconnect = debug_mode; client.on_disconnected(move || { if debug_disconnect { let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); println!("{} [断开] 连接已断开", ts); } }); // 设置错误回调 client.on_error(|error| { eprintln!("[错误] WebSocket: {}", error); }); // 设置首次连接回调(GetFileVer 改到收到 welcome 后发送) let debug_first = debug_mode; let device_number_first = resolve_device_number(); client.on_first_connect(move |_url, _sender| { let device_number = device_number_first.clone(); Box::pin(async move { let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); if device_number.is_empty() || device_number == "UNKNOWN" { if debug_first { println!("{} [连接] 已连接,未配置设备号,仅维持心跳", ts); } } else { if debug_first { println!("{} [连接] 已连接,等待服务器欢迎消息...", ts); } } }) as Pin + Send + Sync>> }); // 设置重连回调 let debug_reconnect = debug_mode; client.on_reconnecting(move |attempt, url_arc| { Box::pin(async move { if debug_reconnect { let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); println!("{} [重连] 第 {} 次重连中...", ts, attempt); } let new_url = resolve_ws_url(); *url_arc.lock().await = new_url.clone(); if debug_reconnect { let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); println!("{} [重连] 配置已更新,服务器: {}", ts, new_url); } }) as Pin + Send + Sync>> }); // 设置重连成功回调 let debug_reconnected = debug_mode; let device_number_reconn = resolve_device_number(); client.on_reconnected(move |_url, _sender| { let device_number = device_number_reconn.clone(); Box::pin(async move { let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); if device_number.is_empty() || device_number == "UNKNOWN" { if debug_reconnected { println!("{} [重连] 已重连,未配置设备号", ts); } } else { if debug_reconnected { println!("{} [重连] 已重连,等待服务器欢迎消息...", ts); } } }) as Pin + Send + Sync>> }); // 连接(CubeLib 会自动处理重连) if debug_mode { println!("[启动] 开始连接..."); } // 等待断连信号或连接正常结束 tokio::select! { _ = &mut disconnect_rx => { // 收到断连信号,主动断开 if debug_mode { println!("[启动] 收到断连信号,主动断开..."); } client.disconnect().await; } _ = client.connect() => { // 连接正常结束(CubeLib 自动重连后的最终退出) if debug_mode { println!("[启动] 连接已结束"); } } } if debug_mode { println!("Updater 本次运行结束"); } // 返回是否执行了更新(下载了文件) let performed = *UPDATE_PERFORMED.lock().unwrap(); performed } #[tokio::main] async fn main() { // 检查是否已有 Updater 进程在运行 if is_process_running("Updater.exe") { return; } // 加载 Updater 自身配置(debug_mode) let config = load_updater_config(); // 非 debug 模式下释放控制台,后台静默运行 if !config.debug_mode { #[cfg(windows)] { use windows::Win32::System::Console; use windows::Win32::Foundation::HWND; unsafe { let console = Console::GetConsoleWindow(); if console != HWND::default() { let _ = Console::FreeConsole(); } } } } // 主循环:常驻运行,定期检查更新 loop { // 运行一次 Updater(连接、检查、下载) let update_performed = run_updater(config.debug_mode).await; if update_performed { // 执行了更新(下载了文件)→ 退出循环,由 BootLoader 重启 Updater if config.debug_mode { println!("Updater 执行了更新,即将退出(等待 BootLoader 重启)"); } break; } // 无更新 → 随机等待 5-10 分钟后再次检查 let wait_seconds = { let mut rng = rand::thread_rng(); let minutes: f64 = rng.gen_range(5.0..=10.0); (minutes * 60.0) as u64 }; if config.debug_mode { let wait_mins = wait_seconds as f64 / 60.0; println!("Updater 本次检查完成,无更新,等待 {:.1} 分钟后再次检查...", wait_mins); } tokio::time::sleep(std::time::Duration::from_secs(wait_seconds)).await; } if config.debug_mode { println!("Updater 已退出"); } }