From 15c0a0419e1ae40fe7924819aa139603aa6f983c Mon Sep 17 00:00:00 2001 From: zqm Date: Fri, 10 Apr 2026 13:28:41 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B9=E4=B8=BA=E8=AF=B7=E6=B1=82-=E5=93=8D?= =?UTF-8?q?=E5=BA=94=E6=A8=A1=E5=BC=8F=EF=BC=8C=E6=AF=8F=E4=B8=AA=20Downlo?= =?UTF-8?q?adFile=20=E5=8F=AA=E8=BF=94=E5=9B=9E=E4=B8=80=E4=B8=AA=20FileCh?= =?UTF-8?q?unk?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Windows/CS/Framework4.0/Updater/src/main.rs | 107 ++++++++++++-------- 1 file changed, 62 insertions(+), 45 deletions(-) diff --git a/Windows/CS/Framework4.0/Updater/src/main.rs b/Windows/CS/Framework4.0/Updater/src/main.rs index f067226..0988cfb 100644 --- a/Windows/CS/Framework4.0/Updater/src/main.rs +++ b/Windows/CS/Framework4.0/Updater/src/main.rs @@ -824,7 +824,7 @@ fn handle_app_file_chunk( data: &serde_json::Map, debug: bool, ) -> Option<(String, String)> { - // 注意:其他应用的文件块,filename 字段就是 relative_path + // 注意:其他应用的文件块,filename 字段就是 relative_path(服务端可能含 app/ 前缀) let filename = data.get("filename").and_then(|v| v.as_str()).unwrap_or(""); if filename.is_empty() { return None; @@ -834,7 +834,17 @@ fn handle_app_file_chunk( let chunk_data = data.get("data").and_then(|v| v.as_str()).unwrap_or(""); let is_last = data.get("is_last").and_then(|v| v.as_bool()).unwrap_or(false); - let key = format!("{}/{}", app_name, filename); + // 服务端 filename 可能含 app/ 前缀(如 "EasyTest/xxx.dll"),统一去掉前缀 + // app_map 中的 key 用 app_name/relative_path 格式存储 + let expected_prefix = format!("{}/", app_name); + let relative_path = if filename.starts_with(&expected_prefix) { + &filename[expected_prefix.len()..] + } else { + filename + }; + let key = format!("{}/{}", app_name, relative_path); + // 记录原始 relative_path,供 is_last=false 时请求下一块使用 + let relative_path = relative_path.to_string(); let mut app_map = ctx.app_download_map.lock().unwrap(); // 获取当前正在下载的文件名(用于判断是否是新文件) @@ -859,21 +869,20 @@ fn handle_app_file_chunk( .and_then(|n| n.to_str()) .unwrap_or(filename); - let app_data_dir = get_updater_data_dir().join(app_name); - let _ = fs::create_dir_all(&app_data_dir); - let final_path = app_data_dir.join(tmp_filename); - let _ = fs::create_dir_all(final_path.parent().unwrap()); - let temp_path = get_updater_data_dir() .join("Updater") .join("UpGrade") .join(app_name) .join(format!("{}.tmp", tmp_filename)); + let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); + println!("{} [应用] 新文件: filename={}, tmp_path={:?}, parent_exists={}", + ts, filename, temp_path, temp_path.parent().map(|p| p.exists()).unwrap_or(false)); + match File::create(&temp_path) { Ok(file) => { app_map.insert(key.clone(), DownloadState { - filename: filename.to_string(), + filename: relative_path.clone(), // 不带 app_name/ 前缀,与 current_filename 检查一致 offset: 0, temp_path: Some(temp_path), file: Some(file), @@ -937,26 +946,27 @@ fn handle_app_file_chunk( let final_path = upgrade_dir.join(final_filename); let _ = fs::create_dir_all(final_path.parent().unwrap()); - if let Err(e) = fs::rename(temp, &final_path) { - if debug { - eprintln!("[应用] 重命名失败 {}: {}", filename, e); - } + // 重命名(同步操作,Windows 上不会异步) + let rename_ok = fs::rename(temp, &final_path).is_ok(); + if !rename_ok { let _ = fs::copy(temp, &final_path); let _ = fs::remove_file(temp); } + let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); + println!("{} [应用] is_last=true: filename={}, temp={:?}, final={:?}, rename_ok={}", + ts, filename, temp, final_path, rename_ok); if debug { - let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); println!("{} [应用] {} 下载完成,共 {} 字节", ts, filename, final_offset); } } app_map.remove(&key); drop(app_map); // 释放锁 - Some((app_name.to_string(), filename.to_string())) + Some((app_name.to_string(), relative_path)) // 返回 relative_path } else { drop(app_map); // 释放锁 - None + Some((app_name.to_string(), relative_path)) // 非最后一块也返回 relative_path,供请求下一块使用 } } @@ -968,19 +978,27 @@ fn handle_app_download_complete( size: u64, debug: bool, ) -> Option<(String, String)> { - let key = format!("{}/{}", app_name, filename); + // 服务端 filename 可能含 app/ 前缀,提取纯相对路径 + let expected_prefix = format!("{}/", app_name); + let relative_path = if filename.starts_with(&expected_prefix) { + &filename[expected_prefix.len()..] + } else { + filename + }; + let key = format!("{}/{}", app_name, relative_path); let mut app_map = ctx.app_download_map.lock().unwrap(); let temp_path = app_map.get(&key).and_then(|s| s.temp_path.clone()); if let Some(ref temp) = temp_path { - let app_data_dir = get_updater_data_dir().join(app_name); // filename 可能含路径分隔符,取纯文件名部分以避免路径重复 let tmp_filename = std::path::Path::new(filename) .file_name() .and_then(|n| n.to_str()) .unwrap_or(filename); - let final_path = app_data_dir.join(tmp_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()); if let Err(e) = fs::rename(temp, &final_path) { @@ -1905,31 +1923,30 @@ async fn run_updater(debug_mode: bool) -> bool { // ========== 阶段3.5:处理应用 FileChunk ========== if current_phase == UpdatePhase::AppsWaitAllFile && !filename.is_empty() { + let is_last = data_obj.get("is_last").and_then(|v| v.as_bool()).unwrap_or(false); + let chunk_offset = data_obj.get("offset").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 candidates = ctx_clone.candidates.lock().unwrap().clone(); for (app_name, _) in &candidates { - // filename 可能含路径分隔符,取纯文件名部分来构造 tmp 路径 - let tmp_filename = std::path::Path::new(filename) - .file_name() - .and_then(|n| n.to_str()) - .unwrap_or(filename); - let upgrade_path = get_updater_data_dir() - .join("Updater") - .join("UpGrade") - .join(app_name) - .join(format!("{}.tmp", tmp_filename)); - if !upgrade_path.exists() { - continue; - } - - let completed = handle_app_file_chunk(&ctx_clone, app_name, data_obj, debug_msg); - if let Some((app, file)) = completed { - if debug_msg { - let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); - println!("{} [应用] {} / {} 下载完成", ts, app, file); + let result = handle_app_file_chunk(&ctx_clone, app_name, data_obj, debug_msg); + if let Some((_, relative_path)) = result { + if is_last { + // 文件下完,标记并请求下一个文件 + if debug_msg { + let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); + println!("{} [应用] {} / {} 下载完成", ts, app_name, filename); + } + ctx_clone.app_completed_set.lock().unwrap().insert(format!("{}/{}", app_name, filename)); + send_next_download(&ctx_clone, &sender); + } else { + // 非最后一块,立即请求下一块(用 relative_path,不带 app_name/ 前缀) + let next_offset = chunk_offset + chunk_size; + if next_offset < file_size { + request_download_for_app(&sender, app_name, &relative_path, next_offset); + } } - ctx_clone.app_completed_set.lock().unwrap().insert(format!("{}/{}", app, file)); - // 一个文件下完,发下一个 - send_next_download(&ctx_clone, &sender); } break; } @@ -1985,13 +2002,13 @@ async fn run_updater(debug_mode: bool) -> bool { .file_name() .and_then(|n| n.to_str()) .unwrap_or(filename); - let tmp_path = get_updater_data_dir() + let upgrade_dir = get_updater_data_dir() .join("Updater") .join("UpGrade") - .join(app_name) - .join(format!("{}.tmp", tmp_filename)); - // rename 后的最终路径(与 handle_app_download_complete 内部保持一致) - let final_path = get_updater_data_dir().join(app_name).join(tmp_filename); + .join(app_name); + let tmp_path = upgrade_dir.join(format!("{}.tmp", tmp_filename)); + // rename 后的最终路径(与 handle_app_file_chunk 的 is_last 块保持一致) + let final_path = upgrade_dir.join(tmp_filename); // tmp 或 final 文件存在才处理 log_print!("{} [DownloadComplete] 检查路径: tmp={:?}, final={:?}, tmp_exists={}, final_exists={}", ts, tmp_path, final_path, tmp_path.exists(), final_path.exists());