改为请求-响应模式,每个 DownloadFile 只返回一个 FileChunk
This commit is contained in:
@@ -824,7 +824,7 @@ fn handle_app_file_chunk(
|
|||||||
data: &serde_json::Map<std::string::String, serde_json::Value>,
|
data: &serde_json::Map<std::string::String, serde_json::Value>,
|
||||||
debug: bool,
|
debug: bool,
|
||||||
) -> Option<(String, String)> {
|
) -> Option<(String, String)> {
|
||||||
// 注意:其他应用的文件块,filename 字段就是 relative_path
|
// 注意:其他应用的文件块,filename 字段就是 relative_path(服务端可能含 app/ 前缀)
|
||||||
let filename = data.get("filename").and_then(|v| v.as_str()).unwrap_or("");
|
let filename = data.get("filename").and_then(|v| v.as_str()).unwrap_or("");
|
||||||
if filename.is_empty() {
|
if filename.is_empty() {
|
||||||
return None;
|
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 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 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();
|
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())
|
.and_then(|n| n.to_str())
|
||||||
.unwrap_or(filename);
|
.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()
|
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(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) {
|
match File::create(&temp_path) {
|
||||||
Ok(file) => {
|
Ok(file) => {
|
||||||
app_map.insert(key.clone(), DownloadState {
|
app_map.insert(key.clone(), DownloadState {
|
||||||
filename: filename.to_string(),
|
filename: relative_path.clone(), // 不带 app_name/ 前缀,与 current_filename 检查一致
|
||||||
offset: 0,
|
offset: 0,
|
||||||
temp_path: Some(temp_path),
|
temp_path: Some(temp_path),
|
||||||
file: Some(file),
|
file: Some(file),
|
||||||
@@ -937,26 +946,27 @@ fn handle_app_file_chunk(
|
|||||||
let final_path = upgrade_dir.join(final_filename);
|
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());
|
||||||
|
|
||||||
if let Err(e) = fs::rename(temp, &final_path) {
|
// 重命名(同步操作,Windows 上不会异步)
|
||||||
if debug {
|
let rename_ok = fs::rename(temp, &final_path).is_ok();
|
||||||
eprintln!("[应用] 重命名失败 {}: {}", filename, e);
|
if !rename_ok {
|
||||||
}
|
|
||||||
let _ = fs::copy(temp, &final_path);
|
let _ = fs::copy(temp, &final_path);
|
||||||
let _ = fs::remove_file(temp);
|
let _ = fs::remove_file(temp);
|
||||||
}
|
}
|
||||||
|
|
||||||
if debug {
|
|
||||||
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!("{} [应用] is_last=true: filename={}, temp={:?}, final={:?}, rename_ok={}",
|
||||||
|
ts, filename, temp, final_path, rename_ok);
|
||||||
|
if debug {
|
||||||
println!("{} [应用] {} 下载完成,共 {} 字节", ts, filename, final_offset);
|
println!("{} [应用] {} 下载完成,共 {} 字节", ts, filename, final_offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
app_map.remove(&key);
|
app_map.remove(&key);
|
||||||
drop(app_map); // 释放锁
|
drop(app_map); // 释放锁
|
||||||
Some((app_name.to_string(), filename.to_string()))
|
Some((app_name.to_string(), relative_path)) // 返回 relative_path
|
||||||
} else {
|
} else {
|
||||||
drop(app_map); // 释放锁
|
drop(app_map); // 释放锁
|
||||||
None
|
Some((app_name.to_string(), relative_path)) // 非最后一块也返回 relative_path,供请求下一块使用
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -968,19 +978,27 @@ fn handle_app_download_complete(
|
|||||||
size: u64,
|
size: u64,
|
||||||
debug: bool,
|
debug: bool,
|
||||||
) -> Option<(String, String)> {
|
) -> 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 mut app_map = ctx.app_download_map.lock().unwrap();
|
||||||
|
|
||||||
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 {
|
||||||
let app_data_dir = get_updater_data_dir().join(app_name);
|
|
||||||
// filename 可能含路径分隔符,取纯文件名部分以避免路径重复
|
// filename 可能含路径分隔符,取纯文件名部分以避免路径重复
|
||||||
let tmp_filename = std::path::Path::new(filename)
|
let tmp_filename = std::path::Path::new(filename)
|
||||||
.file_name()
|
.file_name()
|
||||||
.and_then(|n| n.to_str())
|
.and_then(|n| n.to_str())
|
||||||
.unwrap_or(filename);
|
.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());
|
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) {
|
||||||
@@ -1905,31 +1923,30 @@ async fn run_updater(debug_mode: bool) -> bool {
|
|||||||
|
|
||||||
// ========== 阶段3.5:处理应用 FileChunk ==========
|
// ========== 阶段3.5:处理应用 FileChunk ==========
|
||||||
if current_phase == UpdatePhase::AppsWaitAllFile && !filename.is_empty() {
|
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();
|
let candidates = ctx_clone.candidates.lock().unwrap().clone();
|
||||||
for (app_name, _) in &candidates {
|
for (app_name, _) in &candidates {
|
||||||
// filename 可能含路径分隔符,取纯文件名部分来构造 tmp 路径
|
let result = handle_app_file_chunk(&ctx_clone, app_name, data_obj, debug_msg);
|
||||||
let tmp_filename = std::path::Path::new(filename)
|
if let Some((_, relative_path)) = result {
|
||||||
.file_name()
|
if is_last {
|
||||||
.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 {
|
if debug_msg {
|
||||||
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!("{} [应用] {} / {} 下载完成", ts, app, file);
|
println!("{} [应用] {} / {} 下载完成", ts, app_name, filename);
|
||||||
}
|
}
|
||||||
ctx_clone.app_completed_set.lock().unwrap().insert(format!("{}/{}", app, file));
|
ctx_clone.app_completed_set.lock().unwrap().insert(format!("{}/{}", app_name, filename));
|
||||||
// 一个文件下完,发下一个
|
|
||||||
send_next_download(&ctx_clone, &sender);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1985,13 +2002,13 @@ async fn run_updater(debug_mode: bool) -> bool {
|
|||||||
.file_name()
|
.file_name()
|
||||||
.and_then(|n| n.to_str())
|
.and_then(|n| n.to_str())
|
||||||
.unwrap_or(filename);
|
.unwrap_or(filename);
|
||||||
let tmp_path = get_updater_data_dir()
|
let upgrade_dir = get_updater_data_dir()
|
||||||
.join("Updater")
|
.join("Updater")
|
||||||
.join("UpGrade")
|
.join("UpGrade")
|
||||||
.join(app_name)
|
.join(app_name);
|
||||||
.join(format!("{}.tmp", tmp_filename));
|
let tmp_path = upgrade_dir.join(format!("{}.tmp", tmp_filename));
|
||||||
// rename 后的最终路径(与 handle_app_download_complete 内部保持一致)
|
// rename 后的最终路径(与 handle_app_file_chunk 的 is_last 块保持一致)
|
||||||
let final_path = get_updater_data_dir().join(app_name).join(tmp_filename);
|
let final_path = upgrade_dir.join(tmp_filename);
|
||||||
// tmp 或 final 文件存在才处理
|
// tmp 或 final 文件存在才处理
|
||||||
log_print!("{} [DownloadComplete] 检查路径: tmp={:?}, final={:?}, tmp_exists={}, final_exists={}",
|
log_print!("{} [DownloadComplete] 检查路径: tmp={:?}, final={:?}, tmp_exists={}, final_exists={}",
|
||||||
ts, tmp_path, final_path, tmp_path.exists(), final_path.exists());
|
ts, tmp_path, final_path, tmp_path.exists(), final_path.exists());
|
||||||
|
|||||||
Reference in New Issue
Block a user