升级交互

This commit is contained in:
zqm
2026-04-13 10:00:58 +08:00
parent 6e58857074
commit 0741a56c99
2 changed files with 129 additions and 26 deletions

View File

@@ -12,6 +12,7 @@ windows = { version = "0.56", features = [
"Win32_System_Console",
"Win32_System_Pipes",
"Win32_System_IO",
"Win32_System_Threading",
"Win32_Foundation",
"Win32_Security",
"Win32_Storage",

View File

@@ -1419,20 +1419,48 @@ fn notify_app_upgrade(app_name: &str, current_ver: &str, latest_ver: &str, _debu
app_name, current_ver, latest_ver
);
// 3. 对每个 PID 直接用 Win32 命名管道发送(不再用 PowerShell
// 3. 对每个 PID 发送升级确认并等待用户响应(双向握手
for pid in pids {
let pipe_name = format!("\\\\.\\pipe\\EasyTest_{}", pid);
let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f");
log_print!("{} [升级确认] 连接到 {} (PID={})", ts, pipe_name, pid);
log_print!("{} [升级确认] 连接到 {} (PID={}),等待响应...", ts, pipe_name, pid);
// 直接用 windows-rs Win32 API 连接命名管道
let pipe_result = connect_named_pipe_byte(&pipe_name, &msg);
// 发送 UpgradeConfirm 并等待 UpgradeResponse超时 10 秒)
let response = connect_and_wait_response(&pipe_name, &msg, 10000);
if pipe_result {
log_print!("{} [升级确认] 已通知 {} (PID={}): {} -> {}",
ts, app_name, pid, current_ver, latest_ver);
} else {
log_print!("{} [升级确认] 连接 {} (PID={}) 失败", ts, app_name, pid);
match response {
Ok(true) => {
log_print!("{} [升级确认] 用户批准升级 {} (PID={}),应用将退出",
ts, app_name, pid);
// 等待应用退出(最多 5 秒)
log_print!("{} [升级确认] 等待 {} 退出...", ts, app_name);
let start = std::time::Instant::now();
loop {
let check = Command::new("powershell")
.args(["-NoProfile", "-NonInteractive", "-Command",
&format!("if ((Get-Process -Id {pid} -ErrorAction SilentlyContinue) -eq $null) {{ 'exit' }} else {{ 'running' }}")])
.output();
if let Ok(out) = check {
if String::from_utf8_lossy(&out.stdout).contains("exit") {
log_print!("{} [升级确认] {} 已退出,升级完成", ts, app_name);
break;
}
}
if start.elapsed().as_secs() > 5 {
log_print!("{} [升级确认] 等待退出超时,跳过", ts);
break;
}
std::thread::sleep(std::time::Duration::from_millis(500));
}
}
Ok(false) => {
log_print!("{} [升级确认] 用户拒绝升级 {} (PID={})",
ts, app_name, pid);
}
Err(e) => {
log_print!("{} [升级确认] 通信失败 {} (PID={}): {}",
ts, app_name, pid, e);
}
}
}
}
@@ -1444,14 +1472,21 @@ fn notify_app_upgrade(app_name: &str, current_ver: &str, latest_ver: &str, _debu
}
}
/// 用 Win32 API 连接命名管道发送字节数据(字节模式
fn connect_named_pipe_byte(pipe_name: &str, msg: &str) -> bool {
use windows::Win32::Foundation::{CloseHandle, INVALID_HANDLE_VALUE};
use windows::Win32::Storage::FileSystem::{CreateFileW, WriteFile, FILE_ATTRIBUTE_NORMAL};
/// 用 Win32 API 连接命名管道发送消息后等待响应(双向握手
/// 返回 Ok(true) = 用户批准Ok(false) = 用户拒绝Err = 通信失败/超时
fn connect_and_wait_response(
pipe_name: &str,
msg: &str,
timeout_ms: u32,
) -> Result<bool, String> {
use windows::Win32::Foundation::{CloseHandle, INVALID_HANDLE_VALUE, HANDLE};
use windows::Win32::Storage::FileSystem::{CreateFileW, WriteFile, ReadFile, FILE_ATTRIBUTE_NORMAL};
use windows::Win32::System::Threading::CreateEventW;
use windows::core::PCWSTR;
// Windows API 常量(跨版本稳定)
// Windows API 常量
use windows::Win32::Storage::FileSystem::{FILE_SHARE_MODE, FILE_CREATION_DISPOSITION};
const GENERIC_READ: u32 = 0x80000000u32;
const GENERIC_WRITE: u32 = 0x40000000u32;
const FILE_SHARE_NONE: u32 = 0u32;
const OPEN_EXISTING: u32 = 3u32;
@@ -1459,11 +1494,11 @@ fn connect_named_pipe_byte(pipe_name: &str, msg: &str) -> bool {
// 将管道名转换为宽字符串
let wide_name: Vec<u16> = pipe_name.encode_utf16().chain(std::iter::once(0)).collect();
// 用 CreateFileW 打开命名管道(写模式,字节模式)
// 打开命名管道(同时读写模式)
let pipe = unsafe {
CreateFileW(
PCWSTR::from_raw(wide_name.as_ptr()),
GENERIC_WRITE,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_MODE(FILE_SHARE_NONE),
None,
FILE_CREATION_DISPOSITION(OPEN_EXISTING),
@@ -1472,18 +1507,13 @@ fn connect_named_pipe_byte(pipe_name: &str, msg: &str) -> bool {
)
};
// CreateFileW 返回 Result需要 unwrap
let pipe = match pipe {
Ok(h) => h,
Err(e) => {
eprintln!("[Pipe] CreateFileW failed: {:?}", e);
return false;
}
Err(e) => return Err(format!("连接管道失败: {:?}", e)),
};
// 检查是否成功打开
if pipe == INVALID_HANDLE_VALUE {
return false;
return Err("管道句柄无效".to_string());
}
// 写入消息 + 换行C# 端用 StreamReader.ReadLine 读取)
@@ -1492,7 +1522,7 @@ fn connect_named_pipe_byte(pipe_name: &str, msg: &str) -> bool {
let data = msg_with_newline.as_bytes();
let mut written: u32 = 0;
let success = unsafe {
let write_ok = unsafe {
WriteFile(
pipe,
Some(data),
@@ -1501,9 +1531,81 @@ fn connect_named_pipe_byte(pipe_name: &str, msg: &str) -> bool {
)
};
let _ = unsafe { CloseHandle(pipe) };
if write_ok.is_err() || written == 0 {
unsafe { let _ = CloseHandle(pipe); };
return Err("写入消息失败".to_string());
}
success.is_ok() && written > 0
// 创建事件用于等待响应(失败则用 INVALID_HANDLE_VALUE
let event = unsafe { CreateEventW(None, true, false, None) }
.unwrap_or(HANDLE(0));
let mut buf = [0u8; 4096];
let mut bytes_read: u32 = 0;
let mut resp_buf = Vec::new();
// 循环读取直到收到完整响应(读到 \n或超时
let start_time = std::time::Instant::now();
let deadline = timeout_ms as u64;
loop {
// 检查是否超时
if start_time.elapsed().as_millis() as u64 > deadline {
if event != INVALID_HANDLE_VALUE {
unsafe { let _ = CloseHandle(event); };
}
unsafe { let _ = CloseHandle(pipe); };
return Err("等待响应超时".to_string());
}
// 尝试读取数据(非阻塞轮询)
let result = unsafe {
ReadFile(
pipe,
Some(&mut buf),
Some(&mut bytes_read),
None,
)
};
if result.is_ok() && bytes_read > 0 {
resp_buf.extend_from_slice(&buf[..bytes_read as usize]);
// 检查是否包含换行符(完整消息)
if resp_buf.contains(&b'\n') {
break;
}
}
// 短暂等待后重试
std::thread::sleep(std::time::Duration::from_millis(50));
}
if event != INVALID_HANDLE_VALUE {
unsafe { let _ = CloseHandle(event); };
}
unsafe { let _ = CloseHandle(pipe); };
// 解析响应 JSON
let response = String::from_utf8_lossy(&resp_buf)
.trim_end_matches(|c| c == '\r' || c == '\n')
.to_string();
// 调试:打印原始响应
{
let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f");
log_print!("{} [升级确认] 收到原始响应: {:?}", ts, response);
}
// 解析 {"Type":"UpgradeResponse","Approved":true/false}
if let Some(start) = response.find("\"Approved\":") {
// "Approved": 共11个字符true/false 从 start+11 开始
let rest = &response[start + 11..];
let approved = rest.starts_with("true");
return Ok(approved);
}
Err(format!("无法解析响应: {}", response))
}
/// 通知所有升级了的应用EasyTest 等)