diff --git a/Windows/CS/Framework4.0/Updater/src/main.rs b/Windows/CS/Framework4.0/Updater/src/main.rs index c334a4c..b1cc8aa 100644 --- a/Windows/CS/Framework4.0/Updater/src/main.rs +++ b/Windows/CS/Framework4.0/Updater/src/main.rs @@ -1,4 +1,31 @@ use rand::Rng; +use std::collections::{HashMap, HashSet}; +use std::sync::{Arc, Mutex}; + +// ===================== 更新上下文(局部状态 + Arc 共享)===================== +/// 更新流程的状态上下文 +/// 使用 Mutex 实现 Sync(即使单线程也需要满足 Rust 的线程安全约束) +struct UpdateContext { + /// 当前正在下载的文件状态 + download_state: Mutex>, + /// 其他应用下载状态:key = "app_name/relative_path" + app_download_map: Mutex>, + /// 其他应用更新完成标记:set of "app_name/relative_path" + app_completed_set: Mutex>, + /// Updater.exe 的服务端版本号 + server_updater_version: Mutex>, +} + +impl Default for UpdateContext { + fn default() -> Self { + Self { + download_state: Mutex::new(None), + app_download_map: Mutex::new(HashMap::new()), + app_completed_set: Mutex::new(HashSet::new()), + server_updater_version: Mutex::new(None), + } + } +} use serde::{Deserialize, Serialize}; use std::fs::{self, File}; use std::io::Read; @@ -18,33 +45,6 @@ struct DownloadState { 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); - // ===================== 命名管道服务端 ===================== /// 固定管道名称(单实例运行,直接用此名称) const PIPE_NAME: &str = r"\\.\pipe\Updater"; @@ -281,6 +281,34 @@ fn version_less_than(a: &str, b: &str) -> bool { cube_lib::version_less_than(a, b) } +/// 计算本地文件前 N 字节的 MD5 hash(字节数为 0 表示全部) +fn compute_file_hash_in_dir(app_name: &str, filename: &str, bytes: u64, _debug: bool) -> Option { + let file_path = get_updater_data_dir() + .join("Updater") + .join("UpGrade") + .join(app_name) + .join(filename); + + if !file_path.exists() { + return None; + } + + let mut file = File::open(&file_path).ok()?; + let file_size = file.metadata().ok()?.len(); + + let read_bytes = if bytes == 0 || bytes > file_size { + file_size + } else { + 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)) +} + /// 计算本地文件前 N 字节的 MD5 hash(字节数为 0 表示全部) fn compute_file_hash(filename: &str, bytes: u64, _debug: bool) -> Option { @@ -429,7 +457,11 @@ fn request_download(sender: &cube_lib::websocket::MessageSender, filename: &str, } /// 处理收到的文件块 -fn handle_file_chunk(data: &serde_json::Map, debug: bool) -> Option { +fn handle_file_chunk( + ctx: &Arc, + 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(""); @@ -438,18 +470,24 @@ fn handle_file_chunk(data: &serde_json::Map &s.filename != &final_filename || offset == 0, + None => true, + }; + if need_reset { // 关闭旧文件 - if let Some(f) = state.file.take() { - drop(f); - } - // 删除旧临时文件 - if let Some(ref tp) = state.temp_path { - let _ = fs::remove_file(tp); + if let Some(ref mut s) = *ctx { + if let Some(f) = s.file.take() { + drop(f); + } + if let Some(ref tp) = s.temp_path { + let _ = fs::remove_file(tp); + } } + *ctx = None; // 使用 AppData/Updater/ 作为数据目录 let data_dir = get_updater_data_dir(); @@ -458,10 +496,12 @@ fn handle_file_chunk(data: &serde_json::Map { - state.filename = final_filename.clone(); - state.offset = 0; - state.temp_path = Some(temp_path); - state.file = Some(file); + *ctx = Some(DownloadState { + filename: final_filename.clone(), + offset: 0, + temp_path: Some(temp_path), + file: Some(file), + }); } Err(_) => { if debug { @@ -473,43 +513,46 @@ fn handle_file_chunk(data: &serde_json::Map { - match file.write_all(&decoded) { - Ok(_) => { - let _ = file.flush(); - state.offset += decoded.len() as u64; - } - Err(e) => { - if debug { - eprintln!("[下载] 写入失败: {}", e); + if let Some(ref mut state) = *ctx { + 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); + Err(e) => { + if debug { + eprintln!("[下载] Base64 解码失败: {}", e); + } } } } - } - } else { - if debug { - eprintln!("[下载] 偏移不匹配: 期望 {}, 收到 {}", current_offset, offset); + } else { + if debug { + eprintln!("[下载] 偏移不匹配: 期望 {}, 收到 {}", current_offset, offset); + } } } // 最后一块:重命名临时文件为正式文件 if is_last { - let temp_path = state.temp_path.clone(); + let state = ctx.take().unwrap(); + let temp_path = state.temp_path; let final_offset = state.offset; // 关闭文件句柄 - if let Some(f) = state.file.take() { + if let Some(f) = state.file { drop(f); } @@ -533,12 +576,6 @@ fn handle_file_chunk(data: &serde_json::Map, debug: bool) -> Option { +fn handle_download_complete( + ctx: &Arc, + 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); @@ -559,11 +600,10 @@ fn handle_download_complete(data: &serde_json::Map u64 { + let tmp_path = get_updater_data_dir() + .join("Updater") + .join("UpGrade") + .join(app_name) + .join(format!("{}.tmp", relative_path)); + tmp_path.metadata().map(|m| m.len()).unwrap_or(0) +} + +/// 获取其他应用的本地版本号(通过 PowerShell 读取 PE 版本) +fn get_local_app_version(app_name: &str, relative_path: &str, _debug: bool) -> String { + // 应用主 exe 一般在相对路径的顶级,如 app.exe 或 bin/app.exe + // 取第一段目录名或文件名作为 exe 查找起点 + let file_path = get_updater_data_dir() + .join(app_name) + .join(relative_path); + + 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() + } +} + +/// 向服务器查询指定文件的 md5(用于断点续传校验) +fn request_file_md5_for_app( + sender: &cube_lib::websocket::MessageSender, + app_name: &str, + relative_path: &str, + offset: u64, + debug: bool, +) { + let msg_str = format!( + r#"{{"Type":"GetFileMd5","Data":{{"filename":"{}","bytes":{}}}}}"#, + relative_path, offset + ); + if debug { + let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); + println!("{} [应用] {} 请求 md5 (offset {}): {}", ts, app_name, offset, relative_path); + } + sender.send(msg_str); +} + +/// 向服务器请求下载指定应用的文件 +fn request_download_for_app( + sender: &cube_lib::websocket::MessageSender, + relative_path: &str, + offset: u64, + debug: bool, +) { + let msg_str = format!( + r#"{{"Type":"DownloadFile","Data":{{"filename":"{}","offset":{}}}}}"#, + relative_path, offset + ); + if debug { + let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); + println!("{} [应用] 请求下载: {} (offset {})", ts, relative_path, offset); + } + sender.send(msg_str); +} + +/// 处理其他应用的文件块(写入 AppData/{app_name}/{relative_path}) +fn handle_app_file_chunk( + ctx: &Arc, + app_name: &str, + data: &serde_json::Map, + debug: bool, +) -> Option<(String, String)> { + // 注意:其他应用的文件块,filename 字段就是 relative_path + let filename = data.get("filename").and_then(|v| v.as_str()).unwrap_or(""); + if filename.is_empty() { + return None; + } + + 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); + + let key = format!("{}/{}", app_name, filename); + let mut app_map = ctx.app_download_map.lock().unwrap(); + + // 新文件或 offset=0:重置状态 + if !app_map.contains_key(&key) || offset == 0 { + if let Some(f) = app_map.get_mut(&key) { + if let Some(file) = f.file.take() { + drop(file); + } + if let Some(ref tp) = f.temp_path { + let _ = fs::remove_file(tp); + } + } + app_map.remove(&key); + + let app_data_dir = get_updater_data_dir().join(app_name); + let _ = fs::create_dir_all(&app_data_dir); + let final_path = app_data_dir.join(filename); + let _ = fs::create_dir_all(final_path.parent().unwrap()); + + let temp_path = get_updater_data_dir() + .join("Updater") + .join("UpGrade") + .join(app_name) + .join(format!("{}.tmp", filename)); + + match File::create(&temp_path) { + Ok(file) => { + app_map.insert(key.clone(), DownloadState { + filename: filename.to_string(), + offset: 0, + temp_path: Some(temp_path), + file: Some(file), + }); + } + Err(e) => { + if debug { + eprintln!("[应用] 无法创建临时文件 {}: {}", filename, e); + } + return None; + } + } + } + + let current_offset = app_map.get(&key).map(|s| s.offset).unwrap_or(0); + if offset == current_offset { + if let Some(dl_state) = app_map.get_mut(&key) { + if let Some(ref mut file) = dl_state.file { + match BASE64.decode(chunk_data) { + Ok(decoded) => { + match file.write_all(&decoded) { + Ok(_) => { + let _ = file.flush(); + dl_state.offset += decoded.len() as u64; + } + Err(e) => { + if debug { + eprintln!("[应用] 写入失败 {}: {}", filename, e); + } + } + } + } + Err(e) => { + if debug { + eprintln!("[应用] Base64 解码失败 {}: {}", filename, e); + } + } + } + } + } + } else { + if debug { + eprintln!("[应用] 偏移不匹配 {}: 期望 {}, 收到 {}", filename, current_offset, offset); + } + } + + // 最后一块:原子重命名 + if is_last { + let temp_path = app_map.get(&key).and_then(|s| s.temp_path.clone()); + let final_offset = app_map.get(&key).map(|s| s.offset).unwrap_or(0); + + if let Some(ref temp) = temp_path { + let app_data_dir = get_updater_data_dir().join(app_name); + let final_path = app_data_dir.join(filename); + let _ = fs::create_dir_all(final_path.parent().unwrap()); + + if let Err(e) = fs::rename(temp, &final_path) { + if debug { + eprintln!("[应用] 重命名失败 {}: {}", filename, 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, filename, final_offset); + } + } + + app_map.remove(&key); + drop(app_map); // 释放锁 + Some((app_name.to_string(), filename.to_string())) + } else { + drop(app_map); // 释放锁 + None + } +} + +/// 处理其他应用的文件下载完成(与 FileChunk 互斥,这里主要用于补充处理) +fn handle_app_download_complete( + ctx: &Arc, + app_name: &str, + filename: &str, + size: u64, + debug: bool, +) -> Option<(String, String)> { + let key = format!("{}/{}", app_name, filename); + let mut app_map = ctx.app_download_map.lock().unwrap(); + + let temp_path = app_map.get(&key).and_then(|s| s.temp_path.clone()); + + if let Some(ref temp) = temp_path { + let app_data_dir = get_updater_data_dir().join(app_name); + let final_path = app_data_dir.join(filename); + let _ = fs::create_dir_all(final_path.parent().unwrap()); + + if let Err(e) = fs::rename(temp, &final_path) { + if debug { + eprintln!("[应用] 重命名失败 {}: {}", filename, 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!("{} [应用] {} 下载完成(DownloadComplete),大小: {} 字节", ts, filename, size); + } + } + + app_map.remove(&key); + Some((app_name.to_string(), filename.to_string())) +} + +/// 批量查询所有候选应用的版本号 +fn build_get_file_ver_request(app_names: &[String]) -> Option { + if app_names.is_empty() { + return None; + } + + let mut file_list = Vec::new(); + for name in app_names { + file_list.push(format!("{}.exe", name)); + } + + let file_list_json = serde_json::to_string(&file_list).ok()?; + Some(format!( + r#"{{"Type":"GetFileVer","Data":{{"DeviceNumber":"{}","file_list":{}}}}}"#, + resolve_device_number(), + file_list_json + )) +} + +/// 检查并更新其他应用程序 +/// 返回是否执行了更新 +async fn check_other_apps_updates( + debug: bool, + _shutdown_tx_arc: std::sync::Arc>>>, +) -> bool { + // 创建本次运行的上下文 + let ctx = Arc::new(UpdateContext::default()); + let ctx_clone = ctx.clone(); + + // 标记本次运行是否对其他应用执行了更新 + let other_app_updated = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)); + let other_app_updated_clone = other_app_updated.clone(); + + // 1. 扫描候选应用 + let candidates = get_app_candidates(debug); + if candidates.is_empty() { + if debug { + println!("[应用] 没有正在运行的候选应用,跳过检查"); + } + return false; + } + + if debug { + println!("[应用] 找到 {} 个候选应用: {:?}", candidates.len(), candidates); + } + + // 2. 解析服务器地址 + let server_url = resolve_ws_url(); + let device_number = resolve_device_number(); + + if debug { + println!("[应用] 服务器地址: {}", server_url); + println!("[应用] 设备编号: {}", device_number); + } + + // 3. 创建 WebSocket 连接 + let config = WebSocketConfig::new(&server_url) + .with_client_type("Updater") + .with_debug(debug) + .with_reconnect(false); // 应用更新只查一次,不重连 + + let mut client = WebSocketClient::new(config); + + // 用于存储每个应用是否需要更新的标志 + let apps_to_update = std::sync::Arc::new(std::sync::Mutex::new(Vec::::new())); + let apps_to_update_clone = apps_to_update.clone(); + + // 文件版本响应处理器 + let file_ver_received = std::sync::Arc::new(std::sync::Mutex::new(false)); + let file_ver_clone = file_ver_received.clone(); + + // 设置消息回调 + let debug_msg = debug; + let candidates_clone = candidates.clone(); + client.on_message(move |msg_type, data, sender| { + if msg_type == "welcome" { + // 发送批量版本查询 + if let Some(req) = build_get_file_ver_request(&candidates_clone) { + if debug_msg { + let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); + println!("{} [应用] 发送: {}", ts, req); + } + sender.send(req); + } + } + + if msg_type == "FileVer" { + *file_ver_clone.lock().unwrap() = true; + if let Some(file_versions) = data.get("Data").and_then(|d| d.get("file_versions")).and_then(|v| v.as_object()) { + for app_name in &candidates_clone { + let exe_name = format!("{}.exe", app_name); + if let Some(server_ver) = file_versions.get(&exe_name) { + let server_version = server_ver.as_str().unwrap_or("0.0.0"); + let local_version = get_local_app_version(app_name, &exe_name, debug_msg); + + if debug_msg { + let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); + println!("{} [应用] {}: 服务端={}, 本地={}", ts, app_name, server_version, local_version); + } + + if local_version == "0.0.0" || version_less_than(&local_version, server_version) { + apps_to_update_clone.lock().unwrap().push(app_name.clone()); + + // 扫描升级目录 + let upgrade_files = scan_upgrade_dir(app_name, debug_msg); + if upgrade_files.is_empty() { + if debug_msg { + let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); + println!("{} [应用] {} 没有升级文件,跳过", ts, app_name); + } + continue; + } + + // 对每个文件发起下载(检查断点续传) + for (rel_path, _) in upgrade_files { + let tmp_size = get_app_tmp_file_size(app_name, &rel_path); + if tmp_size > 0 { + // 有临时文件,先 md5 校验 + request_file_md5_for_app(&sender, app_name, &rel_path, tmp_size, debug_msg); + } else { + // 无临时文件,从头下载 + request_download_for_app(&sender, &rel_path, 0, debug_msg); + } + other_app_updated_clone.store(true, std::sync::atomic::Ordering::SeqCst); + } + } + } + } + } + } + + // 处理 md5 响应(其他应用) + if msg_type == "Md5" { + if let Some(md5_data) = data.get("Data").and_then(|v| v.as_object()) { + // Md5 响应的 filename 是相对路径,需要匹配当前查询的应用 + // 注意:这里无法知道是哪个应用的 md5,需要从 candidates 逐一尝试 + // 简化处理:遍历 candidates 找匹配的相对路径 + for app_name in &candidates_clone { + let rel_path = md5_data.get("filename").and_then(|v| v.as_str()).unwrap_or(""); + if rel_path.is_empty() { + continue; + } + + // 检查这个 app 的升级目录是否有这个文件 + let upgrade_path = get_updater_data_dir() + .join("Updater") + .join("UpGrade") + .join(app_name) + .join(rel_path); + if !upgrade_path.exists() { + continue; + } + + 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); + + // 计算本地 tmp 文件的 md5 + let tmp_filename = format!("{}.tmp", rel_path); + let local_md5 = compute_file_hash_in_dir(app_name, &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, rel_path, lm, server_md5); + } + } + + if local_md5.as_deref() == Some(server_md5) { + request_download_for_app(&sender, rel_path, bytes, debug_msg); + } else { + let tmp_path = get_updater_data_dir() + .join("Updater") + .join("UpGrade") + .join(app_name) + .join(&tmp_filename); + let _ = fs::remove_file(&tmp_path); + request_download_for_app(&sender, rel_path, 0, debug_msg); + } + break; // 找到匹配的应用后跳出 + } + } + } + + // 处理文件块(其他应用) + if msg_type == "FileChunk" { + if let Some(data_obj) = data.get("Data").and_then(|v| v.as_object()) { + // 遍历所有候选应用,找匹配的相对路径 + for app_name in &candidates_clone { + let filename = data_obj.get("filename").and_then(|v| v.as_str()).unwrap_or(""); + let upgrade_path = get_updater_data_dir() + .join("Updater") + .join("UpGrade") + .join(app_name) + .join(filename); + if !upgrade_path.exists() { + continue; + } + + let completed = handle_app_file_chunk(&ctx_clone, app_name, data_obj, debug_msg); + if let Some((app, file)) = completed { + if debug_msg { + let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); + println!("{} [应用] {} / {} 下载完成", ts, app, file); + } + ctx_clone.app_completed_set.lock().unwrap().insert(format!("{}/{}", app, file)); + } + break; + } + } + } + + // 处理下载完成(其他应用) + if msg_type == "DownloadComplete" { + if let Some(data_obj) = data.get("Data").and_then(|v| v.as_object()) { + let filename = data_obj.get("filename").and_then(|v| v.as_str()).unwrap_or(""); + if filename.is_empty() { + return; + } + + for app_name in &candidates_clone { + let upgrade_path = get_updater_data_dir() + .join("Updater") + .join("UpGrade") + .join(app_name) + .join(filename); + if !upgrade_path.exists() { + continue; + } + + let size = data_obj.get("size").and_then(|v| v.as_u64()).unwrap_or(0); + handle_app_download_complete(&ctx_clone, app_name, filename, size, debug_msg); + ctx_clone.app_completed_set.lock().unwrap().insert(format!("{}/{}", app_name, filename)); + break; + } + } + } + }); + + // 4. 连接并等待版本查询完成 + if debug { + println!("[应用] 开始连接服务器查询版本..."); + } + + client.connect().await; + + // 等待版本响应(最多等 10 秒) + let start = std::time::Instant::now(); + while start.elapsed().as_secs() < 10 { + if *file_ver_received.lock().unwrap() { + // 收到 FileVer 后,等待下载完成 + tokio::time::sleep(std::time::Duration::from_secs(3)).await; + break; + } + tokio::time::sleep(std::time::Duration::from_millis(100)).await; + } + + if debug { + println!("[应用] 版本查询/下载流程结束"); + } + + other_app_updated.load(std::sync::atomic::Ordering::SeqCst) +} /// 只负责 Updater 自己的行为参数,连接地址从公共 config.json 加载 #[derive(Debug, Serialize, Deserialize)] struct Config { @@ -688,9 +1248,15 @@ fn resolve_device_number() -> String { } fn is_process_running(process_name: &str) -> bool { + is_process_running_ex(process_name, None) +} + +/// 检查进程是否在运行(可指定要排除的 PID) +fn is_process_running_ex(process_name: &str, exclude_pid: Option) -> bool { use std::process::id; - let current_pid = id().to_string(); + let current_pid = id(); + let exclude = exclude_pid.unwrap_or(current_pid); let output = Command::new("tasklist") .args(["/FI", &format!("IMAGENAME eq {}", process_name), "/FO", "CSV"]) .output() @@ -701,14 +1267,116 @@ fn is_process_running(process_name: &str) -> bool { let mut count = 0; for line in lines { - if line.contains(&format!("\"{}\"", process_name)) && !line.contains(¤t_pid) { - count += 1; + if line.contains(&format!("\"{}\"", process_name)) { + // 解析 PID + if let Some(pid_part) = line.split(",").nth(1) { + if let Ok(pid) = pid_part.trim_matches('"').parse::() { + if pid != exclude { + count += 1; + } + } + } } } count > 0 } +/// 获取 AppData 目录下所有候选应用(排除 Updater,返回应用名称列表) +fn get_app_candidates(debug: bool) -> Vec { + let appdata = get_updater_data_dir(); // X:\AppData\ + if !appdata.exists() { + return Vec::new(); + } + + let mut candidates = Vec::new(); + + if let Ok(entries) = fs::read_dir(&appdata) { + for entry in entries.flatten() { + let path = entry.path(); + if path.is_dir() { + if let Some(name) = path.file_name().and_then(|n| n.to_str()) { + if name != "Updater" && !name.starts_with('.') { + // 检查该目录是否对应一个运行中的进程 + let exe_name = format!("{}.exe", name); + if is_process_running(&exe_name) { + if debug { + println!("[应用] 候选应用: {} (进程运行中)", name); + } + candidates.push(name.to_string()); + } else if debug { + println!("[应用] 跳过 {} (进程未运行)", name); + } + } + } + } + } + } + + candidates +} + +/// 递归扫描升级目录,返回 (relative_path, full_path) 列表 +/// 跳过 .tmp 文件(如果存在同名非 tmp 文件的话) +fn scan_upgrade_dir(app_name: &str, debug: bool) -> Vec<(String, PathBuf)> { + let upgrade_base = get_updater_data_dir() + .join("Updater") + .join("UpGrade") + .join(app_name); + + if !upgrade_base.exists() { + if debug { + println!("[应用] {} 升级目录不存在: {:?}", app_name, upgrade_base); + } + return Vec::new(); + } + + let mut files = Vec::new(); + let mut visited_dirs = std::collections::VecDeque::new(); + visited_dirs.push_back(upgrade_base.clone()); + + while let Some(dir) = visited_dirs.pop_front() { + if let Ok(entries) = fs::read_dir(&dir) { + for entry in entries.flatten() { + let path = entry.path(); + if path.is_dir() { + visited_dirs.push_back(path); + } else if path.is_file() { + // 计算相对路径 + if let Ok(rel) = path.strip_prefix(&upgrade_base) { + let rel_str = rel.to_string_lossy().replace('\\', "/"); + let rel_str = rel_str.trim_start_matches('/').to_string(); + + // 如果是 .tmp 文件,检查是否存在对应的非 tmp 文件 + // 如果存在同名非 tmp 文件,则跳过此 tmp 文件 + if rel_str.ends_with(".tmp") { + let non_tmp = rel_str.trim_end_matches(".tmp"); + let non_tmp_path = upgrade_base.join(non_tmp); + if non_tmp_path.exists() { + if debug { + println!("[应用] 跳过 tmp(同名文件已存在): {} -> {}", rel_str, non_tmp); + } + continue; + } + } + + files.push((rel_str, path)); + } + } + } + } + } + + if debug { + println!("[应用] {} 升级文件列表 ({} 个):", app_name, files.len()); + for (rel, _) in &files { + println!("[应用] - {}", rel); + } + } + + files +} + /// 主动断连信号(用于在消息回调中请求优雅退出) /// - shutdown_tx: 从同步回调中发送,通知主循环主动断开 /// - disconnect_rx: 在主循环中等待 @@ -725,11 +1393,24 @@ fn make_shutdown_channel() -> ( /// 运行 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 ctx = Arc::new(UpdateContext::default()); + + // 标记本次运行是否执行了更新(下载了文件) + let update_performed = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)); + let update_performed_clone = update_performed.clone(); + + // 标记 BootLoader 是否已下载(或无需下载) + let bootloader_downloaded = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)); + let bootloader_downloaded_clone = bootloader_downloaded.clone(); + + // 标记 Updater 是否已下载 + let updater_downloaded = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)); + let updater_downloaded_clone = updater_downloaded.clone(); + + // 标记一次更新检查是否已完成 + let update_check_done = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)); + let update_check_done_clone = update_check_done.clone(); // 主动断连通道 let (shutdown_tx_arc, mut disconnect_rx) = make_shutdown_channel(); @@ -768,6 +1449,11 @@ async fn run_updater(debug_mode: bool) -> bool { let debug_msg = debug_mode; let device_number = resolve_device_number(); let shutdown_tx_arc_clone = shutdown_tx_arc.clone(); + let ctx_clone = ctx.clone(); + let update_performed_clone2 = update_performed_clone.clone(); + let bootloader_downloaded_clone2 = bootloader_downloaded_clone.clone(); + let updater_downloaded_clone2 = updater_downloaded_clone.clone(); + let update_check_done_clone2 = update_check_done_clone.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 { @@ -804,7 +1490,7 @@ async fn run_updater(debug_mode: bool) -> bool { 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()); + *ctx_clone.server_updater_version.lock().unwrap() = Some(server_version.to_string()); } } @@ -831,7 +1517,7 @@ async fn run_updater(debug_mode: bool) -> bool { println!("{} [续传] {} 发现未完成下载,请求 hash 校验...", ts, filename); } request_file_md5(&sender, filename, tmp_size, debug_msg); - *UPDATE_PERFORMED.lock().unwrap() = true; + update_performed_clone2.store(true, std::sync::atomic::Ordering::SeqCst); download_initiated = true; } else { // 无临时文件,从头下载 @@ -840,7 +1526,7 @@ async fn run_updater(debug_mode: bool) -> bool { println!("{} [升级] {} 需要更新,开始下载...", ts, filename); } request_download(&sender, filename, 0, debug_msg); - *UPDATE_PERFORMED.lock().unwrap() = true; + update_performed_clone2.store(true, std::sync::atomic::Ordering::SeqCst); download_initiated = true; } } else { @@ -865,7 +1551,7 @@ async fn run_updater(debug_mode: bool) -> bool { // 标记 BootLoader 已"下载完成"(无需更新) if filename == "BootLoader.exe" { - *BOOTLOADER_DOWNLOADED.lock().unwrap() = true; + bootloader_downloaded_clone2.store(true, std::sync::atomic::Ordering::SeqCst); if debug_msg { let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); println!("{} [升级] BootLoader.exe 版本已是最新", ts); @@ -876,7 +1562,7 @@ async fn run_updater(debug_mode: bool) -> bool { // 循环结束后:没有发起下载 → 全部已是最新 if !download_initiated { - *UPDATE_CHECK_DONE.lock().unwrap() = true; + update_check_done_clone2.store(true, std::sync::atomic::Ordering::SeqCst); if debug_msg { let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); println!("{} [升级] 所有文件已是最新版本,等待下次检查...", ts); @@ -934,13 +1620,12 @@ async fn run_updater(debug_mode: bool) -> bool { // 处理文件块 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); + let completed = handle_file_chunk(&ctx_clone, 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 !updater_downloaded_clone2.load(std::sync::atomic::Ordering::SeqCst) { + updater_downloaded_clone2.store(true, std::sync::atomic::Ordering::SeqCst); if debug_msg { let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); println!("{} [升级] Updater.new.exe 下载完成(FileChunk),准备启动 BootLoader...", ts); @@ -955,13 +1640,12 @@ async fn run_updater(debug_mode: bool) -> bool { // 处理下载完成(统一处理,避免 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); + let completed = handle_download_complete(&ctx_clone, 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 !bootloader_downloaded_clone2.load(std::sync::atomic::Ordering::SeqCst) { + bootloader_downloaded_clone2.store(true, std::sync::atomic::Ordering::SeqCst); if debug_msg { let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); println!("{} [升级] BootLoader.exe 下载完成,开始下载 Updater.exe...", ts); @@ -972,9 +1656,8 @@ async fn run_updater(debug_mode: bool) -> bool { } // 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 !updater_downloaded_clone2.load(std::sync::atomic::Ordering::SeqCst) { + updater_downloaded_clone2.store(true, std::sync::atomic::Ordering::SeqCst); if debug_msg { let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); println!("{} [升级] Updater.new.exe 下载完成(DownloadComplete),准备启动 BootLoader...", ts); @@ -1083,8 +1766,7 @@ async fn run_updater(debug_mode: bool) -> bool { } // 返回是否执行了更新(下载了文件) - let performed = *UPDATE_PERFORMED.lock().unwrap(); - performed + update_performed.load(std::sync::atomic::Ordering::SeqCst) } #[tokio::main] @@ -1169,5 +1851,20 @@ async fn main() { } } } + + // 无 BootLoader/Updater 更新时,检查其他应用的更新 + if config.debug_mode { + println!("[应用] 开始检查其他应用程序更新..."); + } + let dummy_shutdown_tx = std::sync::Arc::new(std::sync::Mutex::new(None::>)); + let other_updated = check_other_apps_updates(config.debug_mode, dummy_shutdown_tx).await; + if other_updated { + if config.debug_mode { + println!("[应用] 其他应用程序更新完成"); + } + // 可以选择在这里通知外部(如写日志文件) + } else if config.debug_mode { + println!("[应用] 其他应用程序无需更新"); + } } }