@@ -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 = [ 0 u8 ; 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 , 0 u64 ) ,
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 ) ;
}