下载其它应用
This commit is contained in:
@@ -77,10 +77,11 @@ macro_rules! log_eprintln {
|
|||||||
/// 更新流程的三个阶段
|
/// 更新流程的三个阶段
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
enum UpdatePhase {
|
enum UpdatePhase {
|
||||||
BootLoader, // 阶段1:检查 BootLoader.exe
|
BootLoader, // 阶段1:检查 BootLoader.exe
|
||||||
Updater, // 阶段2:检查 Updater.exe
|
Updater, // 阶段2:检查 Updater.exe
|
||||||
Apps, // 阶段3:检查其他应用
|
Apps, // 阶段3:检查其他应用版本
|
||||||
Complete, // 所有阶段完成
|
AppsWaitAllFile, // 阶段3.5:等待 GetAllFile 响应
|
||||||
|
Complete, // 所有阶段完成
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===================== 更新上下文(局部状态 + Arc 共享)=====================
|
// ===================== 更新上下文(局部状态 + Arc 共享)=====================
|
||||||
@@ -99,6 +100,8 @@ struct UpdateContext {
|
|||||||
current_phase: Mutex<UpdatePhase>,
|
current_phase: Mutex<UpdatePhase>,
|
||||||
/// 候选应用列表(阶段3使用)
|
/// 候选应用列表(阶段3使用)
|
||||||
candidates: Mutex<Vec<String>>,
|
candidates: Mutex<Vec<String>>,
|
||||||
|
/// 等待 GetAllFile 响应的应用列表(阶段3.5使用)
|
||||||
|
pending_allfile_apps: Mutex<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for UpdateContext {
|
impl Default for UpdateContext {
|
||||||
@@ -110,6 +113,7 @@ impl Default for UpdateContext {
|
|||||||
server_updater_version: Mutex::new(None),
|
server_updater_version: Mutex::new(None),
|
||||||
current_phase: Mutex::new(UpdatePhase::BootLoader),
|
current_phase: Mutex::new(UpdatePhase::BootLoader),
|
||||||
candidates: Mutex::new(Vec::new()),
|
candidates: Mutex::new(Vec::new()),
|
||||||
|
pending_allfile_apps: Mutex::new(Vec::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -369,6 +373,7 @@ fn version_less_than(a: &str, b: &str) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 计算本地文件前 N 字节的 MD5 hash(字节数为 0 表示全部)
|
/// 计算本地文件前 N 字节的 MD5 hash(字节数为 0 表示全部)
|
||||||
|
#[allow(dead_code)]
|
||||||
fn compute_file_hash_in_dir(app_name: &str, filename: &str, bytes: u64, _debug: bool) -> Option<String> {
|
fn compute_file_hash_in_dir(app_name: &str, filename: &str, bytes: u64, _debug: bool) -> Option<String> {
|
||||||
let file_path = get_updater_data_dir()
|
let file_path = get_updater_data_dir()
|
||||||
.join("Updater")
|
.join("Updater")
|
||||||
@@ -422,6 +427,17 @@ fn compute_file_hash(filename: &str, bytes: u64, _debug: bool) -> Option<String>
|
|||||||
Some(format!("{:x}", hash))
|
Some(format!("{:x}", hash))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 计算文件的完整 MD5 hash
|
||||||
|
fn compute_file_md5(file_path: &PathBuf) -> Option<String> {
|
||||||
|
if !file_path.exists() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let file_data = fs::read(file_path).ok()?;
|
||||||
|
let hash = md5::compute(&file_data);
|
||||||
|
Some(format!("{:x}", hash))
|
||||||
|
}
|
||||||
|
|
||||||
/// 获取临时文件的当前大小(字节数)
|
/// 获取临时文件的当前大小(字节数)
|
||||||
/// 注意:Updater.exe 的临时文件是 Updater.new.exe.tmp
|
/// 注意:Updater.exe 的临时文件是 Updater.new.exe.tmp
|
||||||
fn get_tmp_file_size(filename: &str) -> u64 {
|
fn get_tmp_file_size(filename: &str) -> u64 {
|
||||||
@@ -723,6 +739,7 @@ fn handle_download_complete(
|
|||||||
// ===================== 其他应用程序更新 =====================
|
// ===================== 其他应用程序更新 =====================
|
||||||
|
|
||||||
/// 获取其他应用文件的临时文件大小
|
/// 获取其他应用文件的临时文件大小
|
||||||
|
#[allow(dead_code)]
|
||||||
fn get_app_tmp_file_size(app_name: &str, relative_path: &str) -> u64 {
|
fn get_app_tmp_file_size(app_name: &str, relative_path: &str) -> u64 {
|
||||||
let tmp_path = get_updater_data_dir()
|
let tmp_path = get_updater_data_dir()
|
||||||
.join("Updater")
|
.join("Updater")
|
||||||
@@ -782,6 +799,7 @@ fn get_local_app_version(app_name: &str, relative_path: &str) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 向服务器查询指定文件的 md5(用于断点续传校验)
|
/// 向服务器查询指定文件的 md5(用于断点续传校验)
|
||||||
|
#[allow(dead_code)]
|
||||||
fn request_file_md5_for_app(
|
fn request_file_md5_for_app(
|
||||||
sender: &cube_lib::websocket::MessageSender,
|
sender: &cube_lib::websocket::MessageSender,
|
||||||
app_name: &str,
|
app_name: &str,
|
||||||
@@ -1135,6 +1153,7 @@ fn get_app_candidates(debug: bool) -> Vec<String> {
|
|||||||
|
|
||||||
/// 递归扫描升级目录,返回 (relative_path, full_path) 列表
|
/// 递归扫描升级目录,返回 (relative_path, full_path) 列表
|
||||||
/// 跳过 .tmp 文件(如果存在同名非 tmp 文件的话)
|
/// 跳过 .tmp 文件(如果存在同名非 tmp 文件的话)
|
||||||
|
#[allow(dead_code)]
|
||||||
fn scan_upgrade_dir(app_name: &str) -> Vec<(String, PathBuf)> {
|
fn scan_upgrade_dir(app_name: &str) -> Vec<(String, PathBuf)> {
|
||||||
let upgrade_base = get_updater_data_dir()
|
let upgrade_base = get_updater_data_dir()
|
||||||
.join("Updater")
|
.join("Updater")
|
||||||
@@ -1188,6 +1207,62 @@ fn scan_upgrade_dir(app_name: &str) -> Vec<(String, PathBuf)> {
|
|||||||
files
|
files
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 扫描本地升级目录,返回 .tmp 文件列表(相对路径)
|
||||||
|
fn scan_local_tmp_files(app_name: &str) -> Vec<String> {
|
||||||
|
let upgrade_base = get_updater_data_dir()
|
||||||
|
.join("Updater")
|
||||||
|
.join("UpGrade")
|
||||||
|
.join(app_name);
|
||||||
|
|
||||||
|
if !upgrade_base.exists() {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut tmp_files = Vec::new();
|
||||||
|
let mut visited_dirs = std::collections::VecDeque::new();
|
||||||
|
visited_dirs.push_back(upgrade_base.clone());
|
||||||
|
|
||||||
|
while let Some(dir) = visited_dirs.pop_front() {
|
||||||
|
if let Ok(entries) = fs::read_dir(&dir) {
|
||||||
|
for entry in entries.flatten() {
|
||||||
|
let path = entry.path();
|
||||||
|
if path.is_dir() {
|
||||||
|
visited_dirs.push_back(path);
|
||||||
|
} else if path.is_file() {
|
||||||
|
if let Some(filename) = path.file_name().and_then(|n| n.to_str()) {
|
||||||
|
if filename.ends_with(".tmp") {
|
||||||
|
// 计算相对路径
|
||||||
|
if let Ok(rel) = path.strip_prefix(&upgrade_base) {
|
||||||
|
let rel_str = rel.to_string_lossy().replace('\\', "/");
|
||||||
|
let rel_str = rel_str.trim_start_matches('/').to_string();
|
||||||
|
tmp_files.push(rel_str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp_files
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 发送 GetAllFile 请求
|
||||||
|
fn send_get_all_file(
|
||||||
|
sender: &cube_lib::websocket::MessageSender,
|
||||||
|
app_name: &str,
|
||||||
|
tmp_files: &[String],
|
||||||
|
) {
|
||||||
|
let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f");
|
||||||
|
let tmp_files_json = serde_json::to_string(tmp_files).unwrap_or_else(|_| "[]".to_string());
|
||||||
|
let msg_str = format!(
|
||||||
|
r#"{{"Type":"GetAllFile","Data":{{"app_name":"{}","tmp_files":{}}}}}"#,
|
||||||
|
app_name, tmp_files_json
|
||||||
|
);
|
||||||
|
log_print!("{} 发送消息:{}", ts, msg_str);
|
||||||
|
sender.send(msg_str);
|
||||||
|
}
|
||||||
|
|
||||||
/// 主动断连信号(用于在消息回调中请求优雅退出)
|
/// 主动断连信号(用于在消息回调中请求优雅退出)
|
||||||
/// - shutdown_tx: 从同步回调中发送,通知主循环主动断开
|
/// - shutdown_tx: 从同步回调中发送,通知主循环主动断开
|
||||||
/// - disconnect_rx: 在主循环中等待
|
/// - disconnect_rx: 在主循环中等待
|
||||||
@@ -1321,6 +1396,9 @@ async fn run_updater(debug_mode: bool) -> bool {
|
|||||||
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");
|
||||||
|
|
||||||
match current_phase {
|
match current_phase {
|
||||||
|
UpdatePhase::AppsWaitAllFile => {
|
||||||
|
// 等待 AllFile 响应,忽略 FileVer
|
||||||
|
}
|
||||||
UpdatePhase::BootLoader => {
|
UpdatePhase::BootLoader => {
|
||||||
// ========== 阶段1:处理 BootLoader ==========
|
// ========== 阶段1:处理 BootLoader ==========
|
||||||
if let Some(server_ver) = file_versions.get("BootLoader.exe") {
|
if let Some(server_ver) = file_versions.get("BootLoader.exe") {
|
||||||
@@ -1458,7 +1536,7 @@ async fn run_updater(debug_mode: bool) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
UpdatePhase::Apps => {
|
UpdatePhase::Apps => {
|
||||||
// ========== 阶段3:处理应用 ==========
|
// ========== 阶段3:处理应用版本,决定是否需要升级 ==========
|
||||||
let candidates = ctx_clone.candidates.lock().unwrap().clone();
|
let candidates = ctx_clone.candidates.lock().unwrap().clone();
|
||||||
let mut apps_to_update = Vec::new();
|
let mut apps_to_update = Vec::new();
|
||||||
|
|
||||||
@@ -1481,39 +1559,24 @@ async fn run_updater(debug_mode: bool) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理需要更新的应用
|
if apps_to_update.is_empty() {
|
||||||
let mut has_downloads = false;
|
// 没有需要更新的应用,所有阶段完成
|
||||||
for app_name in &apps_to_update {
|
log_print!("{} [阶段完成] 所有应用已是最新版本,等待下次检查...", ts);
|
||||||
let upgrade_files = scan_upgrade_dir(app_name);
|
*ctx_clone.current_phase.lock().unwrap() = UpdatePhase::Complete;
|
||||||
if upgrade_files.is_empty() {
|
update_check_done_clone2.store(true, std::sync::atomic::Ordering::SeqCst);
|
||||||
log_print!("{} [应用] {} 没有升级文件,跳过", ts, app_name);
|
if let Some(tx) = shutdown_tx_arc_clone.lock().unwrap().take() {
|
||||||
continue;
|
let _ = tx.send(());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (rel_path, _) in upgrade_files {
|
|
||||||
let tmp_size = get_app_tmp_file_size(app_name, &rel_path);
|
|
||||||
if tmp_size > 0 {
|
|
||||||
request_file_md5_for_app(&sender, app_name, &rel_path, tmp_size);
|
|
||||||
} else {
|
|
||||||
request_download_for_app(&sender, &rel_path, 0);
|
|
||||||
}
|
|
||||||
update_performed_clone2.store(true, std::sync::atomic::Ordering::SeqCst);
|
|
||||||
has_downloads = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 所有阶段完成
|
|
||||||
if !has_downloads {
|
|
||||||
println!("{} [阶段完成] 所有阶段检查完成,等待下次检查...", ts);
|
|
||||||
} else {
|
} else {
|
||||||
println!("{} [阶段完成] 应用下载进行中...", ts);
|
// 存储待处理的应用列表,发送 GetAllFile 请求
|
||||||
}
|
*ctx_clone.pending_allfile_apps.lock().unwrap() = apps_to_update.clone();
|
||||||
*ctx_clone.current_phase.lock().unwrap() = UpdatePhase::Complete;
|
*ctx_clone.current_phase.lock().unwrap() = UpdatePhase::AppsWaitAllFile;
|
||||||
update_check_done_clone2.store(true, std::sync::atomic::Ordering::SeqCst);
|
log_print!("{} [阶段3.5] 等待 GetAllFile 响应 ({} 个应用)", ts, apps_to_update.len());
|
||||||
|
|
||||||
// 发送断连信号
|
for app_name in &apps_to_update {
|
||||||
if let Some(tx) = shutdown_tx_arc_clone.lock().unwrap().take() {
|
let tmp_files = scan_local_tmp_files(app_name);
|
||||||
let _ = tx.send(());
|
send_get_all_file(&sender, app_name, &tmp_files);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1532,50 +1595,6 @@ async fn run_updater(debug_mode: bool) -> bool {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let current_phase = *ctx_clone.current_phase.lock().unwrap();
|
|
||||||
|
|
||||||
// ========== 阶段3:处理应用 Md5 ==========
|
|
||||||
if current_phase == UpdatePhase::Apps {
|
|
||||||
// 遍历候选应用,找匹配的相对路径
|
|
||||||
let candidates = ctx_clone.candidates.lock().unwrap().clone();
|
|
||||||
for app_name in &candidates {
|
|
||||||
let upgrade_path = get_updater_data_dir()
|
|
||||||
.join("Updater")
|
|
||||||
.join("UpGrade")
|
|
||||||
.join(app_name)
|
|
||||||
.join(filename);
|
|
||||||
if !upgrade_path.exists() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let server_md5 = md5_data.get("md5").and_then(|v| v.as_str()).unwrap_or("");
|
|
||||||
let bytes = md5_data.get("bytes").and_then(|v| v.as_u64()).unwrap_or(0);
|
|
||||||
|
|
||||||
let tmp_filename = format!("{}.tmp", filename);
|
|
||||||
let local_md5 = compute_file_hash_in_dir(app_name, &tmp_filename, bytes, debug_msg);
|
|
||||||
|
|
||||||
|
|
||||||
let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f");
|
|
||||||
if let Some(ref lm) = local_md5 {
|
|
||||||
log_print!("{} [应用] {} md5: 本地={}, 服务端={}", ts, filename, lm, server_md5);
|
|
||||||
}
|
|
||||||
|
|
||||||
if local_md5.as_deref() == Some(server_md5) {
|
|
||||||
request_download_for_app(&sender, filename, bytes);
|
|
||||||
} else {
|
|
||||||
let tmp_path = get_updater_data_dir()
|
|
||||||
.join("Updater")
|
|
||||||
.join("UpGrade")
|
|
||||||
.join(app_name)
|
|
||||||
.join(&tmp_filename);
|
|
||||||
let _ = fs::remove_file(&tmp_path);
|
|
||||||
request_download_for_app(&sender, filename, 0);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== BootLoader/Updater 的 Md5 处理 ==========
|
// ========== BootLoader/Updater 的 Md5 处理 ==========
|
||||||
let server_md5 = md5_data.get("md5").and_then(|v| v.as_str()).unwrap_or("");
|
let server_md5 = md5_data.get("md5").and_then(|v| v.as_str()).unwrap_or("");
|
||||||
let bytes = md5_data.get("bytes").and_then(|v| v.as_u64()).unwrap_or(0);
|
let bytes = md5_data.get("bytes").and_then(|v| v.as_u64()).unwrap_or(0);
|
||||||
@@ -1615,6 +1634,191 @@ async fn run_updater(debug_mode: bool) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理 AllFile 响应
|
||||||
|
if msg_type == "AllFile" {
|
||||||
|
if let Some(allfile_data) = data.get("Data").and_then(|v| v.as_object()) {
|
||||||
|
let app_name = allfile_data.get("app_name").and_then(|v| v.as_str()).unwrap_or("");
|
||||||
|
if app_name.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否在等待此应用的响应
|
||||||
|
let mut pending_apps = ctx_clone.pending_allfile_apps.lock().unwrap();
|
||||||
|
if !pending_apps.contains(&app_name.to_string()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pending_apps.retain(|a| a != app_name);
|
||||||
|
|
||||||
|
let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f");
|
||||||
|
log_print!("{} [AllFile] 处理 {} 的文件列表", ts, app_name);
|
||||||
|
|
||||||
|
// 获取服务端文件列表
|
||||||
|
let server_files: Vec<(String, u64, String)> = allfile_data
|
||||||
|
.get("files")
|
||||||
|
.and_then(|v| v.as_array())
|
||||||
|
.map(|arr| {
|
||||||
|
arr.iter().filter_map(|f| {
|
||||||
|
let filename = f.get("filename").and_then(|v| v.as_str())?;
|
||||||
|
let size = f.get("size").and_then(|v| v.as_u64()).unwrap_or(0);
|
||||||
|
let md5 = f.get("md5").and_then(|v| v.as_str()).unwrap_or("");
|
||||||
|
Some((filename.to_string(), size, md5.to_string()))
|
||||||
|
}).collect()
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
// 获取服务端临时文件列表
|
||||||
|
let server_tmp_files: Vec<(String, Option<u64>, Option<String>)> = allfile_data
|
||||||
|
.get("tmp_files")
|
||||||
|
.and_then(|v| v.as_array())
|
||||||
|
.map(|arr| {
|
||||||
|
arr.iter().filter_map(|f| {
|
||||||
|
let filename = f.get("filename").and_then(|v| v.as_str())?;
|
||||||
|
let size = f.get("size").and_then(|v| v.as_u64());
|
||||||
|
let md5 = f.get("md5").and_then(|v| v.as_str());
|
||||||
|
Some((
|
||||||
|
filename.to_string(),
|
||||||
|
size,
|
||||||
|
md5.map(|s| s.to_string()),
|
||||||
|
))
|
||||||
|
}).collect()
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
// 升级目录
|
||||||
|
let upgrade_base = get_updater_data_dir()
|
||||||
|
.join("Updater")
|
||||||
|
.join("UpGrade")
|
||||||
|
.join(app_name);
|
||||||
|
|
||||||
|
// 1. 扫描本地文件
|
||||||
|
let mut local_files: std::collections::HashSet<String> = std::collections::HashSet::new();
|
||||||
|
if upgrade_base.exists() {
|
||||||
|
if let Ok(entries) = fs::read_dir(&upgrade_base) {
|
||||||
|
for entry in entries.flatten() {
|
||||||
|
let path = entry.path();
|
||||||
|
if path.is_file() {
|
||||||
|
if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
|
||||||
|
local_files.insert(name.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 以服务端为准,删除本地多余文件
|
||||||
|
let server_filenames: std::collections::HashSet<String> = server_files
|
||||||
|
.iter()
|
||||||
|
.map(|(name, _, _)| name.clone())
|
||||||
|
.chain(server_tmp_files.iter().map(|(name, _, _)| name.clone()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for local_file in &local_files {
|
||||||
|
if !server_filenames.contains(local_file) {
|
||||||
|
let file_path = upgrade_base.join(local_file);
|
||||||
|
if file_path.exists() {
|
||||||
|
log_print!("{} [AllFile] 删除多余文件: {}", ts, local_file);
|
||||||
|
let _ = fs::remove_file(&file_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 处理临时文件(MD5 不一致则删除)
|
||||||
|
for (tmp_filename, _, server_md5) in &server_tmp_files {
|
||||||
|
let tmp_path = upgrade_base.join(tmp_filename);
|
||||||
|
if tmp_path.exists() {
|
||||||
|
if let Some(sm) = server_md5 {
|
||||||
|
// 计算本地 tmp 文件的 md5(全文件)
|
||||||
|
let local_md5 = compute_file_md5(&tmp_path);
|
||||||
|
if let Some(lm) = local_md5 {
|
||||||
|
if &lm != sm {
|
||||||
|
log_print!("{} [AllFile] tmp {} MD5不一致 (本地={}, 服务端={}),删除", ts, tmp_filename, lm, sm);
|
||||||
|
let _ = fs::remove_file(&tmp_path);
|
||||||
|
} else {
|
||||||
|
log_print!("{} [AllFile] tmp {} MD5一致,跳过", ts, tmp_filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 处理正式文件(MD5 不一致则创建空 tmp 文件)
|
||||||
|
for (filename, _, server_md5) in &server_files {
|
||||||
|
let file_path = upgrade_base.join(filename);
|
||||||
|
let tmp_filename = format!("{}.tmp", filename);
|
||||||
|
let tmp_path = upgrade_base.join(&tmp_filename);
|
||||||
|
|
||||||
|
// 检查是否已有有效的 tmp 文件
|
||||||
|
if tmp_path.exists() {
|
||||||
|
// tmp 已存在,由后续 Md5 响应处理
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查正式文件是否存在
|
||||||
|
if file_path.exists() {
|
||||||
|
// 计算本地文件的 md5
|
||||||
|
let local_md5 = compute_file_md5(&file_path);
|
||||||
|
if let Some(ref lm) = local_md5 {
|
||||||
|
if lm != server_md5 {
|
||||||
|
log_print!("{} [AllFile] {} MD5不一致 (本地={}, 服务端={}),创建 tmp", ts, filename, lm, server_md5);
|
||||||
|
// 先创建目录
|
||||||
|
let _ = fs::create_dir_all(&upgrade_base);
|
||||||
|
// 创建空 tmp 文件标记需要下载
|
||||||
|
if let Ok(_) = File::create(&tmp_path) {
|
||||||
|
log_print!("{} [AllFile] 创建空 tmp 文件: {}", ts, tmp_filename);
|
||||||
|
// 为这个正式文件发送下载请求
|
||||||
|
log_print!("{} [AllFile] 请求下载 {}", ts, filename);
|
||||||
|
request_download_for_app(&sender, filename, 0);
|
||||||
|
update_performed_clone2.store(true, std::sync::atomic::Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log_print!("{} [AllFile] {} MD5一致,无需更新", ts, filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 文件不存在,创建空 tmp
|
||||||
|
log_print!("{} [AllFile] {} 不存在,创建 tmp", ts, filename);
|
||||||
|
// 先创建目录
|
||||||
|
let _ = fs::create_dir_all(&upgrade_base);
|
||||||
|
if let Ok(_) = File::create(&tmp_path) {
|
||||||
|
log_print!("{} [AllFile] 创建空 tmp 文件: {}", ts, tmp_filename);
|
||||||
|
// 为这个正式文件发送下载请求
|
||||||
|
log_print!("{} [AllFile] 请求下载 {}", ts, filename);
|
||||||
|
request_download_for_app(&sender, filename, 0);
|
||||||
|
update_performed_clone2.store(true, std::sync::atomic::Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 发送下载请求(处理服务端返回的 tmp 文件)
|
||||||
|
for (tmp_filename, _, _server_md5) in &server_tmp_files {
|
||||||
|
let tmp_path = upgrade_base.join(tmp_filename);
|
||||||
|
let original_filename = tmp_filename.trim_end_matches(".tmp");
|
||||||
|
|
||||||
|
if tmp_path.exists() {
|
||||||
|
// tmp 存在,发送请求续传
|
||||||
|
let file_size = fs::metadata(&tmp_path).map(|m| m.len()).unwrap_or(0);
|
||||||
|
// log_print!("{} [AllFile] 请求续传 {} (已下载 {} 字节)", ts, original_filename, file_size);
|
||||||
|
request_download_for_app(&sender, original_filename, file_size);
|
||||||
|
} else {
|
||||||
|
// tmp 不存在,重新下载
|
||||||
|
log_print!("{} [AllFile] 请求下载 {}", ts, original_filename);
|
||||||
|
request_download_for_app(&sender, original_filename, 0);
|
||||||
|
}
|
||||||
|
update_performed_clone2.store(true, std::sync::atomic::Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 检查是否所有应用都处理完成
|
||||||
|
if pending_apps.is_empty() {
|
||||||
|
log_print!("{} [AllFile] 所有应用文件同步完成,进入 Complete 阶段", ts);
|
||||||
|
*ctx_clone.current_phase.lock().unwrap() = UpdatePhase::Complete;
|
||||||
|
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(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 处理文件块
|
// 处理文件块
|
||||||
if msg_type == "FileChunk" {
|
if msg_type == "FileChunk" {
|
||||||
if let Some(data_obj) = data.get("Data").and_then(|v| v.as_object()) {
|
if let Some(data_obj) = data.get("Data").and_then(|v| v.as_object()) {
|
||||||
@@ -1622,8 +1826,8 @@ async fn run_updater(debug_mode: bool) -> bool {
|
|||||||
|
|
||||||
let current_phase = *ctx_clone.current_phase.lock().unwrap();
|
let current_phase = *ctx_clone.current_phase.lock().unwrap();
|
||||||
|
|
||||||
// ========== 阶段3:处理应用 FileChunk ==========
|
// ========== 阶段3.5:处理应用 FileChunk ==========
|
||||||
if current_phase == UpdatePhase::Apps && !filename.is_empty() {
|
if current_phase == UpdatePhase::AppsWaitAllFile && !filename.is_empty() {
|
||||||
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 upgrade_path = get_updater_data_dir()
|
let upgrade_path = get_updater_data_dir()
|
||||||
@@ -1675,8 +1879,8 @@ async fn run_updater(debug_mode: bool) -> bool {
|
|||||||
|
|
||||||
let current_phase = *ctx_clone.current_phase.lock().unwrap();
|
let current_phase = *ctx_clone.current_phase.lock().unwrap();
|
||||||
|
|
||||||
// ========== 阶段3:处理应用 DownloadComplete ==========
|
// ========== 阶段3.5:处理应用 DownloadComplete ==========
|
||||||
if current_phase == UpdatePhase::Apps {
|
if current_phase == UpdatePhase::AppsWaitAllFile {
|
||||||
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 upgrade_path = get_updater_data_dir()
|
let upgrade_path = get_updater_data_dir()
|
||||||
|
|||||||
Reference in New Issue
Block a user