315 lines
11 KiB
Rust
315 lines
11 KiB
Rust
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(¤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<Box<dyn std::future::Future<Output = ()> + 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<Box<dyn std::future::Future<Output = ()> + 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<Box<dyn std::future::Future<Output = ()> + 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 已退出");
|
||
}
|
||
}
|