Files
JoyD/Windows/CS/Framework4.0/Updater/src/main.rs
2026-04-08 09:07:08 +08:00

329 lines
11 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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::<Config>(&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::<serde_json::Value>(&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::<serde_json::Value>(&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(&current_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_clone = debug_mode;
client.on_connected(move |url| {
if debug_clone {
println!("[已连接] {}", url);
}
});
// 设置消息接收回调
let debug_for_msg = debug_mode;
client.on_message(move |msg_type, data| {
if debug_for_msg {
// 以标准 JSON 字符串格式输出日志
println!("[消息] {}", serde_json::to_string(&data).unwrap_or_else(|_| data.to_string()));
}
// 处理 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!("[版本] {} = {}", filename, if ver_str.is_empty() { "未知" } else { ver_str });
}
}
}
});
// 设置断开连接回调
let debug_for_disconnect = debug_mode;
client.on_disconnected(move || {
if debug_for_disconnect {
println!("[断开] 连接已断开");
}
});
// 设置错误回调
client.on_error(|error| {
eprintln!("[错误] WebSocket: {}", error);
});
// 设置首次连接回调 - 发送 GetFileVer 命令
let debug_for_first = debug_mode;
let device_number = resolve_device_number();
client.on_first_connect(move |_url, sender| {
let device_number = device_number.clone();
Box::pin(async move {
// 仅当有有效的 DeviceNumber 时才发送 GetFileVer 命令
if device_number.is_empty() || device_number == "UNKNOWN" {
if debug_for_first {
println!("[首次连接] 未配置设备号,仅维持心跳连接");
}
return;
}
if debug_for_first {
println!("[首次连接] 发送 GetFileVer 命令...");
}
// 构造 GetFileVer 消息 - Type在前DeviceNumber从公共配置读取file_list数组
// 注意:手动拼接字符串确保 Type 在 JSON 的第一位serde_json::json! 会按字母排序)
let msg_str = format!(
r#"{{"Type":"GetFileVer","DeviceNumber":"{}","Data":{{"file_list":["BootLoader.exe"]}}}}"#,
device_number
);
if debug_for_first {
println!("[首次连接] GetFileVer 已发送: {}", msg_str);
}
// 通过 sender 发送消息 (使用 .lock().await)
let sender_guard = sender.lock().await;
if let Some(ref tx) = *sender_guard {
let _ = tx.try_send(cube_lib::websocket::OutgoingMessage::Text(msg_str));
}
}) as Pin<Box<dyn std::future::Future<Output = ()> + Send + Sync>>
});
// 设置重连回调 - 在每次重连前重新加载配置
let debug_for_reconnect = debug_mode;
client.on_reconnecting(move |attempt, url_arc| {
Box::pin(async move {
if debug_for_reconnect {
println!("[重连] 第 {} 次重连中...", attempt);
}
// 重新读取配置文件并更新 URL
let new_url = resolve_ws_url();
// 更新 CubeLib 内部的 URL (使用 .lock().await)
*url_arc.lock().await = new_url.clone();
if debug_for_reconnect {
println!("[配置] 已重新加载,服务器地址: {}", new_url);
}
}) as Pin<Box<dyn std::future::Future<Output = ()> + Send + Sync>>
});
// 设置重连成功回调 - 每次重连成功后也发送 GetFileVer
let debug_for_reconnected = debug_mode;
let device_number = resolve_device_number();
client.on_reconnected(move |_url, sender| {
let device_number = device_number.clone();
Box::pin(async move {
// 仅当有有效的 DeviceNumber 时才发送 GetFileVer 命令
if device_number.is_empty() || device_number == "UNKNOWN" {
if debug_for_reconnected {
println!("[重连成功] 未配置设备号,仅维持心跳连接");
}
return;
}
if debug_for_reconnected {
println!("[重连成功] 发送 GetFileVer 命令...");
}
// 构造 GetFileVer 消息 - Type在前DeviceNumber从公共配置读取file_list数组
let msg_str = format!(
r#"{{"Type":"GetFileVer","DeviceNumber":"{}","Data":{{"file_list":["BootLoader.exe"]}}}}"#,
device_number
);
if debug_for_reconnected {
println!("[重连成功] GetFileVer 已发送: {}", msg_str);
}
// 通过 sender 发送消息
let sender_guard = sender.lock().await;
if let Some(ref tx) = *sender_guard {
let _ = tx.try_send(cube_lib::websocket::OutgoingMessage::Text(msg_str));
}
}) as Pin<Box<dyn std::future::Future<Output = ()> + Send + Sync>>
});
// 连接CubeLib 会自动处理重连)
if debug_mode {
println!("[启动] 开始连接...");
}
// connect() 会等待 websocket_loop 完全结束(包括所有重连)
client.connect().await;
if debug_mode {
println!("[启动] 连接已结束");
}
if debug_mode {
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 已退出");
}
}