在非调试模式下不应该弹窗

This commit is contained in:
zqm
2026-05-07 10:13:52 +08:00
parent 4247b49791
commit f620b52fb7
4 changed files with 170 additions and 161 deletions

View File

@@ -3,6 +3,11 @@ name = "Updater"
version = "0.1.0"
edition = "2024"
[profile.release]
debug = false
strip = true
opt-level = 3
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

View File

@@ -3,7 +3,7 @@ use winresource::VersionInfo;
extern crate winresource;
fn main() {
let version_str = "1.0.0.3";
let version_str = "1.0.0.5";
let mut res = winresource::WindowsResource::new();
// 字符串表(文件属性对话框显示的值)

View File

@@ -18,6 +18,24 @@ fn is_debug_mode() -> bool {
DEBUG_MODE.load(std::sync::atomic::Ordering::SeqCst)
}
/// Debug 输出宏:只在 debug 模式下输出到控制台
macro_rules! debug_println {
($($arg:tt)*) => {{
if is_debug_mode() {
println!($($arg)*);
}
}};
}
/// Debug 错误输出宏:只在 debug 模式下输出到控制台
macro_rules! debug_eprintln {
($($arg:tt)*) => {{
if is_debug_mode() {
eprintln!($($arg)*);
}
}};
}
/// 日志文件Mutex 确保线程安全写入)
static LOG_FILE: std::sync::Mutex<Option<std::fs::File>> = std::sync::Mutex::new(None);
@@ -53,11 +71,11 @@ fn init_log_file() {
.open(&log_path)
{
Ok(file) => {
eprintln!("[日志] 日志文件: {:?}", log_path);
debug_eprintln!("[日志] 日志文件: {:?}", log_path);
*guard = Some(file);
}
Err(e) => {
eprintln!("[日志] 无法打开日志文件: {}", e);
debug_eprintln!("[日志] 无法打开日志文件: {}", e);
}
}
}
@@ -164,6 +182,12 @@ use std::process::Command;
use std::io::Write as StdWrite;
use std::io::Seek;
#[cfg(windows)]
use std::os::windows::process::CommandExt;
/// CREATE_NO_WINDOW 常量:防止从 GUI 子系统进程启动控制台程序时闪现黑色窗口
const CREATE_NO_WINDOW: u32 = 0x08000000;
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
use cube_lib::websocket::{WebSocketClient, WebSocketConfig};
@@ -180,7 +204,7 @@ struct DownloadState {
const PIPE_NAME: &str = r"\\.\pipe\Updater";
/// 从命名管道读取一行消息(阻塞,需在 spawn_blocking 中调用)
fn read_pipe_message(pipe: windows::Win32::Foundation::HANDLE, debug: bool) -> Option<String> {
fn read_pipe_message(pipe: windows::Win32::Foundation::HANDLE, _debug: bool) -> Option<String> {
use windows::Win32::Storage::FileSystem::ReadFile;
let mut buf = [0u8; 4096];
@@ -196,9 +220,7 @@ fn read_pipe_message(pipe: windows::Win32::Foundation::HANDLE, debug: bool) -> O
};
if result.is_err() {
if debug {
eprintln!("[Pipe] 读取失败");
}
debug_eprintln!("[Pipe] 读取失败");
return None;
}
@@ -210,15 +232,13 @@ fn read_pipe_message(pipe: windows::Win32::Foundation::HANDLE, debug: bool) -> O
.trim_end()
.to_string();
if debug {
println!("[Pipe] 收到消息:{}", msg);
}
debug_println!("[Pipe] 收到消息:{}", msg);
Some(msg)
}
/// 写入 ACK 响应到命名管道
fn write_pipe_ack(pipe: windows::Win32::Foundation::HANDLE, debug: bool) {
fn write_pipe_ack(pipe: windows::Win32::Foundation::HANDLE) {
use windows::Win32::Storage::FileSystem::WriteFile;
let ack = b"ACK\r\n";
@@ -232,8 +252,8 @@ fn write_pipe_ack(pipe: windows::Win32::Foundation::HANDLE, debug: bool) {
)
};
if result.is_err() && debug {
eprintln!("[Pipe] 写入 ACK 失败");
if result.is_err() {
debug_eprintln!("[Pipe] 写入 ACK 失败");
}
}
@@ -247,9 +267,7 @@ fn start_named_pipe_server(
) -> tokio::task::JoinHandle<()> {
tokio::task::spawn(async move {
let pipe_name = PIPE_NAME;
if debug {
println!("[Pipe] 命名管道服务端启动:{}", pipe_name);
}
debug_println!("[Pipe] 命名管道服务端启动:{}", pipe_name);
loop {
// 在 blocking 线程中创建并等待客户端连接
@@ -276,31 +294,27 @@ fn start_named_pipe_server(
};
if pipe == windows::Win32::Foundation::INVALID_HANDLE_VALUE {
eprintln!("[Pipe] CreateNamedPipeW 失败");
debug_eprintln!("[Pipe] CreateNamedPipeW 失败");
return None;
}
if debug {
println!("[Pipe] 等待客户端连接...");
}
debug_println!("[Pipe] 等待客户端连接...");
// 阻塞等待客户端连接
let connected = unsafe { ConnectNamedPipe(pipe, None) };
if connected.is_err() {
eprintln!("[Pipe] ConnectNamedPipe 失败");
debug_eprintln!("[Pipe] ConnectNamedPipe 失败");
return None;
}
if debug {
println!("[Pipe] 客户端已连接");
}
debug_println!("[Pipe] 客户端已连接");
// 读取消息
let msg = read_pipe_message(pipe, debug);
// 回复 ACK
if msg.is_some() {
write_pipe_ack(pipe, debug);
write_pipe_ack(pipe);
}
// 断开连接
@@ -318,9 +332,7 @@ fn start_named_pipe_server(
if tx.send(m).await.is_err() {
break;
}
if debug {
println!("[Pipe] quit 已转发,退出监听循环");
}
debug_println!("[Pipe] quit 已转发,退出监听循环");
break;
}
@@ -339,9 +351,7 @@ fn start_named_pipe_server(
}
}
if debug {
println!("[Pipe] 命名管道服务端已停止");
}
debug_println!("[Pipe] 命名管道服务端已停止");
})
}
@@ -377,6 +387,7 @@ fn get_local_file_version(filename: &str) -> String {
let output = Command::new("powershell")
.args(["-NoProfile", "-NonInteractive", "-Command", &ps_script])
.creation_flags(CREATE_NO_WINDOW)
.output();
if let Ok(output) = output {
@@ -501,7 +512,7 @@ fn has_updater_new_exe() -> bool {
/// 安排启动 BootLoader.exe延迟执行等待所有下载完成
/// 通过 shutdown_tx 发送断连信号,通知主循环优雅退出
fn schedule_bootloader_launch(
debug: bool,
_debug: bool,
shutdown_tx_arc: std::sync::Arc<std::sync::Mutex<Option<tokio::sync::oneshot::Sender<()>>>>,
) {
// 延迟 1 秒后启动,确保文件句柄已关闭
@@ -513,22 +524,16 @@ fn schedule_bootloader_launch(
let bootloader_path = get_updater_data_dir().join("BootLoader.exe");
if !bootloader_path.exists() {
if debug {
println!("{} [错误] BootLoader.exe 不存在,无法启动", ts);
}
debug_println!("{} [错误] BootLoader.exe 不存在,无法启动", ts);
return;
}
// 启动 BootLoader.exe
if debug {
println!("{} [启动] 正在启动 BootLoader.exe...", ts);
}
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"])
@@ -536,9 +541,7 @@ fn schedule_bootloader_launch(
.spawn()
{
Ok(_) => {
if debug {
println!("{} [启动] BootLoader.exe 已启动Updater 即将退出", ts);
}
debug_println!("{} [启动] BootLoader.exe 已启动Updater 即将退出", ts);
// 发送断连信号,通知主循环优雅退出
if let Some(tx) = shutdown_tx_arc.lock().unwrap().take() {
let _ = tx.send(());
@@ -561,9 +564,7 @@ fn schedule_bootloader_launch(
.spawn()
{
Ok(_) => {
if debug {
println!("{} [启动] BootLoader.exe 已启动Updater 即将退出", ts);
}
debug_println!("{} [启动] BootLoader.exe 已启动Updater 即将退出", ts);
if let Some(tx) = shutdown_tx_arc.lock().unwrap().take() {
let _ = tx.send(());
}
@@ -605,7 +606,7 @@ fn request_download(sender: &cube_lib::websocket::MessageSender, filename: &str,
fn handle_file_chunk(
ctx: &Arc<UpdateContext>,
data: &serde_json::Map<std::string::String, serde_json::Value>,
debug: bool,
_debug: bool,
) -> Option<String> {
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);
@@ -647,17 +648,13 @@ fn handle_file_chunk(
match OpenOptions::new().write(true).append(true).open(&temp_path) {
Ok(f) => {
let current_size = f.metadata().map(|m| m.len()).unwrap_or(0);
if debug {
eprintln!("[下载] 续传:追加打开 {:?}, 当前大小={}", temp_path, current_size);
}
debug_eprintln!("[下载] 续传:追加打开 {:?}, 当前大小={}", temp_path, current_size);
let mut file = f;
file.seek(std::io::SeekFrom::Start(current_size)).ok();
(file, current_size)
}
Err(e) => {
if debug {
eprintln!("[下载] 无法追加打开临时文件 {}: {}", filename, e);
}
debug_eprintln!("[下载] 无法追加打开临时文件 {}: {}", filename, e);
return None;
}
}
@@ -666,9 +663,7 @@ fn handle_file_chunk(
match File::create(&temp_path) {
Ok(file) => (file, 0u64),
Err(e) => {
if debug {
eprintln!("[下载] 无法创建临时文件 {}: {}", filename, e);
}
debug_eprintln!("[下载] 无法创建临时文件 {}: {}", filename, e);
return None;
}
}
@@ -695,23 +690,17 @@ fn handle_file_chunk(
state.offset += decoded.len() as u64;
}
Err(e) => {
if debug {
eprintln!("[下载] 写入失败: {}", e);
}
debug_eprintln!("[下载] 写入失败: {}", e);
}
}
}
Err(e) => {
if debug {
eprintln!("[下载] Base64 解码失败: {}", e);
}
debug_eprintln!("[下载] Base64 解码失败: {}", e);
}
}
}
} else {
if debug {
eprintln!("[下载] 偏移不匹配: 期望 {}, 收到 {}", current_offset, offset);
}
debug_eprintln!("[下载] 偏移不匹配: 期望 {}, 收到 {}", current_offset, offset);
}
}
@@ -732,18 +721,14 @@ fn handle_file_chunk(
// 原子重命名
if let Err(e) = fs::rename(temp, &final_path) {
if debug {
eprintln!("[下载] 重命名失败: {}", e);
}
debug_eprintln!("[下载] 重命名失败: {}", 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, final_filename, final_offset);
}
debug_println!("{} [下载] {} 下载完成,共 {} 字节", ts, final_filename, final_offset);
}
// 返回下载完成的文件名,用于后续流程判断
@@ -757,7 +742,7 @@ fn handle_file_chunk(
fn handle_download_complete(
ctx: &Arc<UpdateContext>,
data: &serde_json::Map<std::string::String, serde_json::Value>,
debug: bool,
_debug: bool,
) -> Option<String> {
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);
@@ -765,10 +750,8 @@ fn handle_download_complete(
// 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, final_filename, size);
}
debug_println!("{} [下载] {} 下载完成,最终大小: {} 字节", ts, final_filename, size);
let mut ctx = ctx.download_state.lock().unwrap();
@@ -787,18 +770,14 @@ fn handle_download_complete(
// 原子重命名
if let Err(e) = fs::rename(&temp_path_owned, &final_path) {
if debug {
eprintln!("[下载] 重命名失败: {}", e);
}
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);
}
debug_println!("{} [下载] {} 临时文件已重命名", ts, final_filename);
} else {
// 没有临时文件,只重置状态
*ctx = None;
@@ -912,7 +891,7 @@ fn handle_app_file_chunk(
ctx: &Arc<UpdateContext>,
app_name: &str,
data: &serde_json::Map<std::string::String, serde_json::Value>,
debug: bool,
_debug: bool,
) -> Option<(String, String)> {
// 注意其他应用的文件块filename 字段就是 relative_path服务端可能含 app/ 前缀)
let filename = data.get("filename").and_then(|v| v.as_str()).unwrap_or("");
@@ -981,9 +960,7 @@ fn handle_app_file_chunk(
});
}
Err(e) => {
if debug {
eprintln!("[应用] 无法创建临时文件 {}: {}", filename, e);
}
debug_eprintln!("[应用] 无法创建临时文件 {}: {}", filename, e);
return None;
}
}
@@ -1001,24 +978,18 @@ fn handle_app_file_chunk(
dl_state.offset += decoded.len() as u64;
}
Err(e) => {
if debug {
eprintln!("[应用] 写入失败 {}: {}", filename, e);
}
debug_eprintln!("[应用] 写入失败 {}: {}", filename, e);
}
}
}
Err(e) => {
if debug {
eprintln!("[应用] Base64 解码失败 {}: {}", filename, e);
}
debug_eprintln!("[应用] Base64 解码失败 {}: {}", filename, e);
}
}
}
}
} else {
if debug {
eprintln!("[应用] 偏移不匹配 {}: 期望 {}, 收到 {}", filename, current_offset, offset);
}
debug_eprintln!("[应用] 偏移不匹配 {}: 期望 {}, 收到 {}", filename, current_offset, offset);
}
// 最后一块:原子重命名
@@ -1048,9 +1019,7 @@ fn handle_app_file_chunk(
let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f");
log_print!("{} [应用] 文件下载完成: filename={}, final_path={:?}, rename_ok={}, final_exists={}",
ts, filename, final_path, rename_ok, final_path.exists());
if debug {
println!("{} [应用] {} 下载完成,共 {} 字节", ts, filename, final_offset);
}
debug_println!("{} [应用] {} 下载完成,共 {} 字节", ts, filename, final_offset);
}
app_map.remove(&key);
@@ -1068,7 +1037,7 @@ fn handle_app_download_complete(
app_name: &str,
filename: &str,
size: u64,
debug: bool,
_debug: bool,
) -> Option<(String, String)> {
// 服务端 filename 可能含 app/ 前缀,提取纯相对路径
let expected_prefix = format!("{}/", app_name);
@@ -1097,10 +1066,8 @@ fn handle_app_download_complete(
log_print!("{} [应用] 重命名成功 {} -> {:?}", ts, filename, final_path);
}
if debug {
let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f");
println!("{} [应用] {} 下载完成DownloadComplete大小: {} 字节", ts, filename, size);
}
debug_println!("{} [应用] {} 下载完成DownloadComplete大小: {} 字节", ts, filename, size);
}
app_map.remove(&key);
@@ -1207,6 +1174,7 @@ fn is_process_running_ex(process_name: &str, exclude_pid: Option<u32>) -> bool {
let exclude = exclude_pid.unwrap_or(current_pid);
let output = Command::new("tasklist")
.args(["/FI", &format!("IMAGENAME eq {}", process_name), "/FO", "CSV"])
.creation_flags(CREATE_NO_WINDOW)
.output()
.expect("Failed to execute tasklist");
@@ -1233,7 +1201,7 @@ fn is_process_running_ex(process_name: &str, exclude_pid: Option<u32>) -> bool {
/// 获取 AppData 目录下所有候选应用(排除 Updater
/// 返回 (app_name, local_version, exe_path) 列表
/// 版本号和路径在进程运行期间直接从进程路径读取,保证与进程实际加载的一致
fn get_app_candidates(debug: bool) -> Vec<(String, String, String)> {
fn get_app_candidates(_debug: bool) -> Vec<(String, String, String)> {
let appdata = get_updater_data_dir(); // X:\AppData\
if !appdata.exists() {
return Vec::new();
@@ -1250,12 +1218,10 @@ fn get_app_candidates(debug: bool) -> Vec<(String, String, String)> {
let exe_name = format!("{}.exe", name);
if is_process_running(&exe_name) {
let (local_version, exe_path) = get_version_and_path_from_process(name);
if debug {
println!("[应用] 候选应用: {} v{} ({})", name, local_version, exe_path);
}
debug_println!("[应用] 候选应用: {} v{} ({})", name, local_version, exe_path);
candidates.push((name.to_string(), local_version, exe_path));
} else if debug {
println!("[应用] 跳过 {} (进程未运行)", name);
} else {
debug_println!("[应用] 跳过 {} (进程未运行)", name);
}
}
}
@@ -1286,6 +1252,7 @@ fn get_version_and_path_from_process(app_name: &str) -> (String, String) {
let output = Command::new("powershell")
.args(["-NoProfile", "-NonInteractive", "-Command", &ps_script])
.creation_flags(CREATE_NO_WINDOW)
.output();
if let Ok(output) = output {
@@ -1556,6 +1523,7 @@ fn notify_app_upgrade(app_name: &str, current_ver: &str, latest_ver: &str, _debu
);
let output = Command::new("powershell")
.args(["-NoProfile", "-NonInteractive", "-Command", &ps_script])
.creation_flags(CREATE_NO_WINDOW)
.output();
let pids: Vec<u32> = match output {
@@ -1756,6 +1724,7 @@ fn notify_all_app_upgrades(app_upgrades: &[(String, String, String, String)], _d
let check = Command::new("powershell")
.args(["-NoProfile", "-NonInteractive", "-Command",
&format!("if ((Get-Process -Name '{}' -ErrorAction SilentlyContinue) -eq $null) {{ 'exit' }} else {{ 'running' }}", app_name)])
.creation_flags(CREATE_NO_WINDOW)
.output();
if let Ok(out) = check {
@@ -1832,16 +1801,16 @@ async fn run_updater(debug_mode: bool) -> bool {
if debug_mode {
let local_version = get_local_file_version("Updater.exe");
println!("========================================");
println!("Updater 启动 (调试模式)");
println!("当前版本: {}", local_version);
println!("服务器地址: {}", server_url);
println!("自动重连: 启用 (指数退避: 1s - 30s)");
println!("更新阶段: BootLoader -> Updater -> Apps -> Complete");
debug_println!("========================================");
debug_println!("Updater 启动 (调试模式)");
debug_println!("当前版本: {}", local_version);
debug_println!("服务器地址: {}", server_url);
debug_println!("自动重连: 启用 (指数退避: 1s - 30s)");
debug_println!("更新阶段: BootLoader -> Updater -> Apps -> Complete");
if !app_candidates.is_empty() {
println!("候选应用: {:?}", app_candidates);
debug_println!("候选应用: {:?}", app_candidates);
}
println!("========================================");
debug_println!("========================================");
}
// 创建 WebSocket 配置(启用自动重连和心跳)
@@ -1861,7 +1830,7 @@ async fn run_updater(debug_mode: bool) -> bool {
client.on_connected(move |url| {
if debug_connected {
let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f");
println!("{} 收到消息:{}", ts, url);
debug_println!("{} 收到消息:{}", ts, url);
}
});
@@ -2066,7 +2035,7 @@ async fn run_updater(debug_mode: bool) -> bool {
let candidates = ctx_clone.candidates.lock().unwrap().clone();
if candidates.is_empty() {
*ctx_clone.current_phase.lock().unwrap() = UpdatePhase::Complete;
println!("{} [阶段3] 没有候选应用,所有阶段完成", ts);
log_print!("{} [阶段3] 没有候选应用,所有阶段完成", ts);
update_check_done_clone2.store(true, std::sync::atomic::Ordering::SeqCst);
if let Some(tx) = shutdown_tx_arc_clone.lock().unwrap().take() {
let _ = tx.send(());
@@ -2075,7 +2044,7 @@ async fn run_updater(debug_mode: bool) -> bool {
*ctx_clone.current_phase.lock().unwrap() = UpdatePhase::Apps;
// 打印候选应用及其预存版本
let app_names: Vec<String> = candidates.iter().map(|(n, v, _)| format!("{}(v{})", n, v)).collect();
println!("{} [阶段3] 检查应用: {:?}", ts, app_names);
log_print!("{} [阶段3] 检查应用: {:?}", ts, app_names);
let mut file_list = Vec::new();
for (app_name, _, _) in &candidates {
@@ -2174,9 +2143,9 @@ async fn run_updater(debug_mode: bool) -> bool {
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, filename, lm, server_md5);
debug_println!("{} [续传] {} md5对比: 本地={}, 服务端={}", ts, filename, lm, server_md5);
} else {
println!("{} [续传] {} 无法计算本地md5重新下载", ts, filename);
debug_println!("{} [续传] {} 无法计算本地md5重新下载", ts, filename);
}
}
@@ -2424,7 +2393,7 @@ async fn run_updater(debug_mode: bool) -> bool {
// 文件下完,标记并请求下一个文件
if debug_msg {
let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f");
println!("{} [应用] {} / {} 下载完成", ts, app_name, filename);
debug_println!("{} [应用] {} / {} 下载完成", ts, app_name, filename);
}
ctx_clone.app_completed_set.lock().unwrap().insert(format!("{}/{}", app_name, filename));
log_print!("{} [FileChunk] 调用 send_next_download, queue_len={}", ts, queue_len);
@@ -2461,7 +2430,7 @@ async fn run_updater(debug_mode: bool) -> bool {
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);
debug_println!("{} [升级] Updater.new.exe 下载完成FileChunk准备启动 BootLoader...", ts);
}
schedule_bootloader_launch(debug_msg, shutdown_tx_arc_clone.clone());
}
@@ -2607,7 +2576,7 @@ async fn run_updater(debug_mode: bool) -> bool {
client.on_disconnected(move || {
if debug_disconnect {
let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f");
println!("{} [断开] 连接已断开", ts);
debug_println!("{} [断开] 连接已断开", ts);
}
});
@@ -2625,11 +2594,11 @@ async fn run_updater(debug_mode: bool) -> bool {
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);
debug_println!("{} [连接] 已连接,未配置设备号,仅维持心跳", ts);
}
} else {
if debug_first {
println!("{} [连接] 已连接,等待服务器欢迎消息...", ts);
debug_println!("{} [连接] 已连接,等待服务器欢迎消息...", ts);
}
}
}) as Pin<Box<dyn std::future::Future<Output = ()> + Send + Sync>>
@@ -2641,13 +2610,13 @@ async fn run_updater(debug_mode: bool) -> bool {
Box::pin(async move {
if debug_reconnect {
let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f");
println!("{} [重连] 第 {} 次重连中...", ts, attempt);
debug_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);
debug_println!("{} [重连] 配置已更新,服务器: {}", ts, new_url);
}
}) as Pin<Box<dyn std::future::Future<Output = ()> + Send + Sync>>
});
@@ -2661,11 +2630,11 @@ async fn run_updater(debug_mode: bool) -> bool {
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);
debug_println!("{} [重连] 已重连,未配置设备号", ts);
}
} else {
if debug_reconnected {
println!("{} [重连] 已重连,等待服务器欢迎消息...", ts);
debug_println!("{} [重连] 已重连,等待服务器欢迎消息...", ts);
}
}
}) as Pin<Box<dyn std::future::Future<Output = ()> + Send + Sync>>
@@ -2676,13 +2645,13 @@ async fn run_updater(debug_mode: bool) -> bool {
client.on_heartbeat_ack(move |latency_ms, server_timestamp| {
if debug_heartbeat {
let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f");
println!("{} [心跳] pong响应延迟: {}ms, 服务端时间: {}", ts, latency_ms, server_timestamp);
debug_println!("{} [心跳] pong响应延迟: {}ms, 服务端时间: {}", ts, latency_ms, server_timestamp);
}
});
// 连接CubeLib 会自动处理重连)
if debug_mode {
println!("[启动] 开始连接...");
debug_println!("[启动] 开始连接...");
}
// 等待断连信号或连接正常结束
@@ -2690,20 +2659,20 @@ async fn run_updater(debug_mode: bool) -> bool {
_ = &mut disconnect_rx => {
// 收到断连信号,主动断开
if debug_mode {
println!("[启动] 收到断连信号,主动断开...");
debug_println!("[启动] 收到断连信号,主动断开...");
}
client.disconnect().await;
}
_ = client.connect() => {
// 连接正常结束CubeLib 自动重连后的最终退出)
if debug_mode {
println!("[启动] 连接已结束");
debug_println!("[启动] 连接已结束");
}
}
}
if debug_mode {
println!("Updater 本次运行结束");
debug_println!("Updater 本次运行结束");
}
// 在退出前,通知所有升级的应用(发送升级确认消息)
@@ -2730,7 +2699,7 @@ async fn main() {
// 设置全局 debug 模式标志(必须在任何日志输出之前设置)
set_debug_mode(config.debug_mode);
// debug 模式下分配控制台窗口
// debug 模式下分配控制台窗口,非 debug 模式下重定向标准流到空设备
if config.debug_mode {
#[cfg(windows)]
{
@@ -2739,6 +2708,41 @@ async fn main() {
let _ = Console::AllocConsole();
}
}
} else {
#[cfg(windows)]
{
// 重定向标准流到空设备,防止子进程意外输出到控制台
use windows::Win32::Storage::FileSystem::{CreateFileW, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_MODE, FILE_CREATION_DISPOSITION};
use windows::Win32::System::Console::SetStdHandle;
use windows::Win32::Foundation::INVALID_HANDLE_VALUE;
use windows::core::PCWSTR;
const GENERIC_READ: u32 = 0x80000000;
const GENERIC_WRITE: u32 = 0x40000000;
const OPEN_EXISTING: u32 = 3;
unsafe {
// 打开空设备
let null_name: Vec<u16> = "NUL".encode_utf16().chain(std::iter::once(0)).collect();
let null_handle = CreateFileW(
PCWSTR::from_raw(null_name.as_ptr()),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_MODE(0),
None,
FILE_CREATION_DISPOSITION(OPEN_EXISTING),
FILE_ATTRIBUTE_NORMAL,
None,
);
if let Ok(handle) = null_handle {
if handle != INVALID_HANDLE_VALUE {
// 重定向 stdin, stdout, stderr
let _ = SetStdHandle(windows::Win32::System::Console::STD_INPUT_HANDLE, handle);
let _ = SetStdHandle(windows::Win32::System::Console::STD_OUTPUT_HANDLE, handle);
let _ = SetStdHandle(windows::Win32::System::Console::STD_ERROR_HANDLE, handle);
}
}
}
}
}
// 启动命名管道服务端(长生命周期 async task
@@ -2752,7 +2756,7 @@ async fn main() {
if updater_updated {
if config.debug_mode {
println!("Updater 自身已更新,即将退出(等待 BootLoader 重启)");
debug_println!("Updater 自身已更新,即将退出(等待 BootLoader 重启)");
}
std::process::exit(0);
}
@@ -2784,12 +2788,12 @@ async fn main() {
msg = pipe_rx.recv() => {
if let Some(m) = msg {
if config.debug_mode {
println!("[Pipe] 主循环收到消息:{}", m);
debug_println!("[Pipe] 主循环收到消息:{}", m);
}
// quit 命令 → 退出
if m == "quit" {
if config.debug_mode {
println!("[Pipe] 收到 quit 命令Updater 退出");
debug_println!("[Pipe] 收到 quit 命令Updater 退出");
}
std::process::exit(0);
}

View File

@@ -1 +1 @@
{"rustc_fingerprint":17656983458485528297,"outputs":{"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.94.1 (e408947bf 2026-03-25)\nbinary: rustc\ncommit-hash: e408947bfd200af42db322daf0fadfe7e26d3bd1\ncommit-date: 2026-03-25\nhost: x86_64-pc-windows-msvc\nrelease: 1.94.1\nLLVM version: 21.1.8\n","stderr":""},"8372365128484698959":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\xyzqm\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"cmpxchg16b\"\ntarget_feature=\"crt-static\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"pc\"\nwindows\n","stderr":""},"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\xyzqm\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"cmpxchg16b\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"pc\"\nwindows\n","stderr":""}},"successes":{}}
{"rustc_fingerprint":17656983458485528297,"outputs":{"8372365128484698959":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\xyzqm\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"cmpxchg16b\"\ntarget_feature=\"crt-static\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"pc\"\nwindows\n","stderr":""},"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\xyzqm\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"cmpxchg16b\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"pc\"\nwindows\n","stderr":""},"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.94.1 (e408947bf 2026-03-25)\nbinary: rustc\ncommit-hash: e408947bfd200af42db322daf0fadfe7e26d3bd1\ncommit-date: 2026-03-25\nhost: x86_64-pc-windows-msvc\nrelease: 1.94.1\nLLVM version: 21.1.8\n","stderr":""}},"successes":{}}