From 4e761f5ddcf0aef81d083badca3e090193b9761c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=95=E6=9F=8F=E9=9D=92?= Date: Tue, 16 Jun 2026 12:41:17 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix(local-asr):=20=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E4=B8=8B=E8=BD=BD=E3=80=8C=E5=B7=B2=E5=AD=98=E5=9C=A8=E5=8D=B3?= =?UTF-8?q?=E8=B7=B3=E8=BF=87=E3=80=8D=E6=94=B9=E4=B8=BA=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=E5=A4=A7=E5=B0=8F=EF=BC=8C=E6=8B=92=E6=94=B6=E6=88=AA=E6=96=AD?= =?UTF-8?q?/=E6=8D=9F=E5=9D=8F=E6=96=87=E4=BB=B6=20(#686)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit run_download 此前仅凭 dest.exists() 跳过已存在文件,不比对 file.size——上次被 中断或外部损坏而残留的截断文件会被当成「已下完」跳过,最终在模型加载时以含糊 错误失败。 抽纯函数 existing_file_is_complete(actual, expected):大小一致才算完整; expected==0(HF 未给大小)退回「存在即信任」;元数据取不到算不完整。run_download 据此判定,大小不符则删除残留文件重新下载。附真值表单测。 --- .../app/src-tauri/src/asr/local/download.rs | 49 ++++++++++++++++++- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/openless-all/app/src-tauri/src/asr/local/download.rs b/openless-all/app/src-tauri/src/asr/local/download.rs index fa4d4e90..6941a5e1 100644 --- a/openless-all/app/src-tauri/src/asr/local/download.rs +++ b/openless-all/app/src-tauri/src/asr/local/download.rs @@ -242,6 +242,17 @@ pub(crate) fn build_client() -> Result { builder.build().context("build reqwest client failed") } +/// 判断目录里已存在的目标文件是否可信为「完整下载」。 +/// `actual_size` 为 `None`(取不到元数据)一律视为不完整。`expected_size == 0` 表示 HF 未 +/// 给出该文件大小(未知)→ 退回旧行为:只要文件存在就信任,避免对未知大小的文件反复重下。 +/// 否则严格要求字节数一致——上次被中断/外部损坏而残留的截断文件不再被当作完整跳过。 +fn existing_file_is_complete(actual_size: Option, expected_size: u64) -> bool { + match actual_size { + Some(actual) => expected_size == 0 || actual == expected_size, + None => false, + } +} + async fn run_download( app: &AppHandle, model_id: ModelId, @@ -322,8 +333,20 @@ async fn run_download( for (idx, file) in info.files.iter().enumerate() { let dest = dir.join(&file.path); if dest.exists() { - // 已经下完的(目录里直接存在 dest 文件)跳过;前面 already_done_bytes 已计入 - continue; + let actual = std::fs::metadata(&dest).ok().map(|m| m.len()); + if existing_file_is_complete(actual, file.size) { + // 已完整下载(大小一致,或 HF 未给大小时退回信任存在)→ 跳过; + // already_done_bytes 已计入。 + continue; + } + // 大小不符:上次被中断 / 外部损坏的截断文件,删除后重新下载,避免被当成完整。 + log::warn!( + "[download] 已存在文件 {} 大小 {:?} != 期望 {},删除重下", + file.path, + actual, + file.size + ); + let _ = std::fs::remove_file(&dest); } let url = format!( "{}/{}/resolve/main/{}", @@ -998,3 +1021,25 @@ fn emit_cancelled( }, ); } + +#[cfg(test)] +mod tests { + use super::existing_file_is_complete; + + #[test] + fn existing_file_complete_only_when_size_matches_or_unknown() { + // 大小一致 → 完整。 + assert!(existing_file_is_complete(Some(1024), 1024)); + // 截断(偏小)→ 不完整,触发删除重下。 + assert!(!existing_file_is_complete(Some(512), 1024)); + // 比期望大(损坏 / 串写)→ 不完整。 + assert!(!existing_file_is_complete(Some(2048), 1024)); + // 取不到元数据 → 不完整。 + assert!(!existing_file_is_complete(None, 1024)); + // HF 未给大小(expected == 0)→ 退回「只要存在就信任」。 + assert!(existing_file_is_complete(Some(0), 0)); + assert!(existing_file_is_complete(Some(123), 0)); + // expected == 0 但元数据取不到 → 仍不完整。 + assert!(!existing_file_is_complete(None, 0)); + } +} From 4023724bf89fe1370e151775d2a9e4fd169dd112 Mon Sep 17 00:00:00 2001 From: sim Date: Tue, 16 Jun 2026 14:28:14 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix(local-asr):=20already=5Fdone=5Fbytes=20?= =?UTF-8?q?=E5=8F=AA=E8=AE=A1=E5=AE=8C=E6=95=B4=E6=96=87=E4=BB=B6=EF=BC=8C?= =?UTF-8?q?=E4=BF=AE=E8=A2=AB=E5=88=A0=E5=9D=8F=E6=96=87=E4=BB=B6=E7=9A=84?= =?UTF-8?q?=E8=BF=9B=E5=BA=A6=E5=8F=8C=E8=AE=A1=20(#686)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 审核指出:大小不符的坏文件被删除重下时,already_done_bytes 已按 f.size 把它计入, 重下时 in_flight 又从 0 涨到 f.size,导致进度条短暂双计 / >100%(纯 UI,最终态正确)。 改为只把 existing_file_is_complete 为真的文件计入,与「完整才跳过、否则删重下」一致。 --- openless-all/app/src-tauri/src/asr/local/download.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openless-all/app/src-tauri/src/asr/local/download.rs b/openless-all/app/src-tauri/src/asr/local/download.rs index 6941a5e1..8ad38f0e 100644 --- a/openless-all/app/src-tauri/src/asr/local/download.rs +++ b/openless-all/app/src-tauri/src/asr/local/download.rs @@ -319,7 +319,11 @@ async fn run_download( .iter() .map(|f| { let d = dir.join(&f.path); - if d.exists() { + // 只把「确实完整」的已存在文件计入已完成字节,与下面 run_download 的「完整才跳过、 + // 否则删除重下」一致。若对大小不符的坏文件也按 f.size 计入,重下时 in_flight 又从 0 + // 涨到 f.size,会双重计数、进度条短暂 >100%。 + let actual = std::fs::metadata(&d).ok().map(|m| m.len()); + if existing_file_is_complete(actual, f.size) { f.size } else { 0