diff --git a/Windows/CS/Framework4.0/Updater/build.rs b/Windows/CS/Framework4.0/Updater/build.rs index 8e875a2..5fe9398 100644 --- a/Windows/CS/Framework4.0/Updater/build.rs +++ b/Windows/CS/Framework4.0/Updater/build.rs @@ -2,7 +2,7 @@ extern crate winres; fn main() { let mut res = winres::WindowsResource::new(); - res.set("FileVersion", "0.0.0.1"); + res.set("FileVersion", "0.0.0.2"); res.set("ProductVersion", "0.0.0.1"); res.compile().unwrap(); } \ No newline at end of file diff --git a/Windows/CS/Framework4.0/Updater/src/main.rs b/Windows/CS/Framework4.0/Updater/src/main.rs index 8f74fb1..1539bab 100644 --- a/Windows/CS/Framework4.0/Updater/src/main.rs +++ b/Windows/CS/Framework4.0/Updater/src/main.rs @@ -35,6 +35,11 @@ static DOWNLOAD_STATE: std::sync::Mutex = std::sync::Mutex::new(D 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); + // ===================== 版本比较与下载 ===================== /// 获取 Updater 数据目录(X:\AppData\,存放 BootLoader.exe 等) fn get_updater_data_dir() -> PathBuf { @@ -97,26 +102,34 @@ fn get_local_file_version(filename: &str) -> String { /// 比较两个版本号(a < b 返回 true) fn version_less_than(a: &str, b: &str) -> bool { + // 按 '.' 分割版本号 let parse_v = |v: &str| -> Vec { - v.split(|c: char| !c.is_ascii_digit() && c != '.') + v.split('.') .filter(|s| !s.is_empty()) .filter_map(|s| s.parse().ok()) .collect() }; let a_parts: Vec = parse_v(a); let b_parts: Vec = parse_v(b); + + eprintln!("[DEBUG] version_less_than: a={:?} -> {:?}", a, a_parts); + eprintln!("[DEBUG] version_less_than: b={:?} -> {:?}", b, b_parts); + let max_len = a_parts.len().max(b_parts.len()); for i in 0..max_len { let av = if i < a_parts.len() { a_parts[i] } else { 0 }; let bv = if i < b_parts.len() { b_parts[i] } else { 0 }; if av < bv { + eprintln!("[DEBUG] version_less_than: {}[{}] < {}[{}] = true", a, i, b, i); return true; } if av > bv { + eprintln!("[DEBUG] version_less_than: {}[{}] > {}[{}] = false", a, i, b, i); return false; } } + eprintln!("[DEBUG] version_less_than: {} >= {} = false", a, b); false } @@ -147,11 +160,163 @@ fn compute_file_hash(filename: &str, bytes: u64, _debug: bool) -> Option } /// 获取临时文件的当前大小(字节数) +/// 注意:Updater.exe 的临时文件是 Updater.new.exe.tmp fn get_tmp_file_size(filename: &str) -> u64 { - let tmp_path = get_updater_data_dir().join(format!("{}.tmp", filename)); + 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(延迟执行,等待所有下载完成) +fn schedule_bootloader_launch(debug: bool) { + // 延迟 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); + } + // 给自己发送退出信号(优雅退出) + std::process::exit(0); + } + Err(e) => { + eprintln!("{} [错误] 启动 BootLoader.exe 失败: {}", ts, e); + // 仍然退出 + std::process::exit(1); + } + } + } + + #[cfg(not(windows))] + { + match Command::new(&bootloader_path) + .arg("--from-updater") + .spawn() + { + Ok(_) => { + if debug { + println!("{} [启动] BootLoader.exe 已启动,Updater 即将退出", ts); + } + std::process::exit(0); + } + Err(e) => { + eprintln!("{} [错误] 启动 BootLoader.exe 失败: {}", ts, e); + std::process::exit(1); + } + } + } + }); +} + /// 发送 GetFileMd5 请求 fn request_file_md5(sender: &cube_lib::websocket::MessageSender, filename: &str, bytes: u64, debug: bool) { let msg_str = format!( @@ -179,16 +344,19 @@ fn request_download(sender: &cube_lib::websocket::MessageSender, filename: &str, } /// 处理收到的文件块 -fn handle_file_chunk(data: &serde_json::Map, debug: bool) { +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 != filename || offset == 0 { + if state.filename != final_filename || offset == 0 { // 关闭旧文件 if let Some(f) = state.file.take() { drop(f); @@ -200,12 +368,12 @@ fn handle_file_chunk(data: &serde_json::Map { - state.filename = filename.to_string(); + state.filename = final_filename.clone(); state.offset = 0; state.temp_path = Some(temp_path); state.file = Some(file); @@ -214,7 +382,7 @@ fn handle_file_chunk(data: &serde_json::Map, debug: bool) { +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, filename, size); + println!("{} [下载] {} 下载完成,最终大小: {} 字节", ts, final_filename, size); } let mut state = DOWNLOAD_STATE.lock().unwrap(); - state.filename = String::new(); - state.offset = 0; - state.temp_path = None; - state.file = None; + + // 如果有临时文件,执行重命名(防止 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) @@ -465,11 +673,11 @@ async fn run_updater(debug_mode: bool) { ); } - // 收到 welcome 后,发送 GetFileVer + // 收到 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"]}}}}"#, + r#"{{"Type":"GetFileVer","Data":{{"DeviceNumber":"{}","file_list":["BootLoader.exe","Updater.exe"]}}}}"#, device_number ); if debug_msg { @@ -483,6 +691,16 @@ async fn run_updater(debug_mode: bool) { // 处理 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); @@ -490,7 +708,7 @@ async fn run_updater(debug_mode: bool) { if debug_msg { let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); - println!("{} [版本] {}: 服务端={}, 本地={}, tmp大小={}", + println!("{} [版本] {}: 服务端={}, 本地={}, tmp大小={}", ts, filename, server_version, local_version, tmp_size); } @@ -512,6 +730,55 @@ async fn run_updater(debug_mode: bool) { } request_download(&sender, filename, 0, debug_msg); } + } 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 { + // 真的不需要更新,也没有临时文件 → 退出 + if debug_msg { + let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); + println!("{} [升级] 所有文件已是最新版本,Updater 退出", ts); + } + std::process::exit(0); + } + } + } } } } @@ -523,7 +790,12 @@ async fn run_updater(debug_mode: bool) { 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); - let tmp_filename = format!("{}.tmp", filename); + // 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); @@ -561,14 +833,55 @@ async fn run_updater(debug_mode: bool) { // 处理文件块 if msg_type == "FileChunk" { if let Some(data_obj) = data.get("Data").and_then(|v| v.as_object()) { - handle_file_chunk(data_obj, debug_msg); + 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); + } + } + } } } - // 处理下载完成 + // 处理下载完成(统一处理,避免 FileChunk 和 DownloadComplete 重复处理) if msg_type == "DownloadComplete" { if let Some(data_obj) = data.get("Data").and_then(|v| v.as_object()) { - handle_download_complete(data_obj, debug_msg); + 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); + } + } + } } } });