handle_app_file_chunk 函数在创建 .tmp 文件时,使用 file_name() 提取纯文件名,导致丢失了子目录信息(如 x64/、x86/)

This commit is contained in:
zqm
2026-04-10 14:06:19 +08:00
parent 15c0a0419e
commit 457e5c5333

View File

@@ -6,7 +6,7 @@ use std::sync::{Arc, Mutex};
/// 日志文件Mutex 确保线程安全写入) /// 日志文件Mutex 确保线程安全写入)
static LOG_FILE: std::sync::Mutex<Option<std::fs::File>> = std::sync::Mutex::new(None); static LOG_FILE: std::sync::Mutex<Option<std::fs::File>> = std::sync::Mutex::new(None);
/// 初始化日志文件(以追加方式打开) /// 初始化日志文件(启动时清除当天日志,然后以追加方式打开)
fn init_log_file() { fn init_log_file() {
let mut guard = LOG_FILE.lock().unwrap(); let mut guard = LOG_FILE.lock().unwrap();
if guard.is_some() { if guard.is_some() {
@@ -23,7 +23,10 @@ fn init_log_file() {
}; };
let log_name = format!("Updater_{}.log", chrono::Local::now().format("%Y%m%d")); let log_name = format!("Updater_{}.log", chrono::Local::now().format("%Y%m%d"));
let log_path = log_dir.join(log_name); let log_path = log_dir.join(&log_name);
// 启动时清除当天的日志文件
let _ = std::fs::remove_file(&log_path);
match std::fs::OpenOptions::new() match std::fs::OpenOptions::new()
.create(true) .create(true)
@@ -814,6 +817,16 @@ fn send_next_download(ctx: &Arc<UpdateContext>, sender: &cube_lib::websocket::Me
request_download_for_app(sender, &app_name, &filename, offset); request_download_for_app(sender, &app_name, &filename, offset);
} else { } else {
*ctx.is_downloading.lock().unwrap() = false; *ctx.is_downloading.lock().unwrap() = false;
// 队列为空,检查是否所有应用都处理完了
let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f");
let pending_apps = ctx.pending_allfile_apps.lock().unwrap();
if pending_apps.is_empty() {
log_print!("{} [下载] 所有应用文件下载完成,进入 Complete 阶段", ts);
*ctx.current_phase.lock().unwrap() = UpdatePhase::Complete;
} else {
log_print!("{} [下载] 队列为空,等待 {} 个应用完成 AllFile 响应", ts, pending_apps.len());
}
} }
} }
@@ -863,17 +876,14 @@ fn handle_app_file_chunk(
} }
app_map.remove(&key); app_map.remove(&key);
// filename 可能含路径分隔符,取纯文件名部分 // relative_path 已经是去掉 app_name/ 前缀的相对路径(如 "x64/SQLite.Interop.dll"
let tmp_filename = std::path::Path::new(filename) // 直接用它构建路径,保留子目录信息
.file_name()
.and_then(|n| n.to_str())
.unwrap_or(filename);
let temp_path = get_updater_data_dir() let temp_path = get_updater_data_dir()
.join("Updater") .join("Updater")
.join("UpGrade") .join("UpGrade")
.join(app_name) .join(app_name)
.join(format!("{}.tmp", tmp_filename)); .join(&relative_path)
.with_extension("tmp");
let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f");
println!("{} [应用] 新文件: filename={}, tmp_path={:?}, parent_exists={}", println!("{} [应用] 新文件: filename={}, tmp_path={:?}, parent_exists={}",
@@ -935,15 +945,8 @@ fn handle_app_file_chunk(
let final_offset = app_map.get(&key).map(|s| s.offset).unwrap_or(0); let final_offset = app_map.get(&key).map(|s| s.offset).unwrap_or(0);
if let Some(ref temp) = temp_path { if let Some(ref temp) = temp_path {
// filename 来自服务端消息,可能含路径前缀(如 "sticker/app_config.json" // final_path 是 temp_path 去掉 ".tmp" 后缀,直接在同一目录
// 只取纯文件名,与 DownloadComplete 处理器保持路径一致 let final_path = temp.with_extension(""); // 去掉 .tmp 后缀
let final_filename = std::path::Path::new(filename)
.file_name()
.and_then(|n| n.to_str())
.unwrap_or(filename);
// final_path 与 tmp 文件同一目录(升级目录),不是 app_data_dir
let upgrade_dir = temp.parent().unwrap();
let final_path = upgrade_dir.join(final_filename);
let _ = fs::create_dir_all(final_path.parent().unwrap()); let _ = fs::create_dir_all(final_path.parent().unwrap());
// 重命名同步操作Windows 上不会异步) // 重命名同步操作Windows 上不会异步)
@@ -991,14 +994,8 @@ fn handle_app_download_complete(
let temp_path = app_map.get(&key).and_then(|s| s.temp_path.clone()); let temp_path = app_map.get(&key).and_then(|s| s.temp_path.clone());
if let Some(ref temp) = temp_path { if let Some(ref temp) = temp_path {
// filename 可能含路径分隔符,取纯文件名部分以避免路径重复 // final_path 是 temp_path 去掉 ".tmp" 后缀,直接在同一目录
let tmp_filename = std::path::Path::new(filename) let final_path = temp.with_extension(""); // 去掉 .tmp 后缀
.file_name()
.and_then(|n| n.to_str())
.unwrap_or(filename);
// final_path 与 tmp 文件同一目录(升级目录),与 handle_app_file_chunk 的 is_last 块保持一致
let upgrade_dir = temp.parent().unwrap();
let final_path = upgrade_dir.join(tmp_filename);
let _ = fs::create_dir_all(final_path.parent().unwrap()); let _ = fs::create_dir_all(final_path.parent().unwrap());
if let Err(e) = fs::rename(temp, &final_path) { if let Err(e) = fs::rename(temp, &final_path) {
@@ -1928,6 +1925,11 @@ async fn run_updater(debug_mode: bool) -> bool {
let file_size = data_obj.get("file_size").and_then(|v| v.as_u64()).unwrap_or(0); let file_size = data_obj.get("file_size").and_then(|v| v.as_u64()).unwrap_or(0);
let chunk_size = 4096u64; let chunk_size = 4096u64;
// 调试:打印队列状态
let queue_len = ctx_clone.download_queue.lock().unwrap().len();
let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f");
log_print!("{} [FileChunk] is_last={}, queue_len={}", ts, is_last, queue_len);
let candidates = ctx_clone.candidates.lock().unwrap().clone(); let candidates = ctx_clone.candidates.lock().unwrap().clone();
for (app_name, _) in &candidates { for (app_name, _) in &candidates {
let result = handle_app_file_chunk(&ctx_clone, app_name, data_obj, debug_msg); let result = handle_app_file_chunk(&ctx_clone, app_name, data_obj, debug_msg);
@@ -1939,6 +1941,7 @@ async fn run_updater(debug_mode: bool) -> bool {
println!("{} [应用] {} / {} 下载完成", ts, app_name, filename); println!("{} [应用] {} / {} 下载完成", ts, app_name, filename);
} }
ctx_clone.app_completed_set.lock().unwrap().insert(format!("{}/{}", 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);
send_next_download(&ctx_clone, &sender); send_next_download(&ctx_clone, &sender);
} else { } else {
// 非最后一块,立即请求下一块(用 relative_path不带 app_name/ 前缀) // 非最后一块,立即请求下一块(用 relative_path不带 app_name/ 前缀)