use serde::{Deserialize, Serialize}; use std::fs; use std::path::PathBuf; use std::pin::Pin; use std::process::Command; use cube_lib::websocket::{WebSocketClient, WebSocketConfig}; /// 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 { 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 updater_dir = appdata.join("Updater"); let _ = fs::create_dir_all(&updater_dir); updater_dir.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 } /// 运行 Updater(使用 CubeLib 内置的自动重连) async fn run_updater(debug_mode: bool) { // 加载初始 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(); client.on_message(move |msg_type, data, sender| { let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); if debug_msg { // 收到消息日志:Type 在前 let data_str = serde_json::to_string(&data).unwrap_or_else(|_| "{}".to_string()); println!("{} 收到消息:{{\"Type\":{},\"Data\":{}}}", ts, serde_json::to_string(&msg_type).unwrap_or_default(), data_str ); } // 收到 welcome 后,发送 GetFileVer 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"]}}}}"#, 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, version) in file_versions { let ver_str = version.as_str().unwrap_or(""); println!("{} [版本] {} = {}", ts, filename, if ver_str.is_empty() { "未知" } else { ver_str }); } } } }); // 设置断开连接回调 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!("[启动] 开始连接..."); } client.connect().await; if debug_mode { println!("[启动] 连接已结束"); println!("Updater 已停止"); } } #[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(); } } } } // 运行 Updater run_updater(config.debug_mode).await; if config.debug_mode { println!("Updater 已退出"); } }