feat(update): 支持更新包断点续传#247
Open
penguin-madagascar wants to merge 2 commits into
Open
Conversation
Contributor
There was a problem hiding this comment.
Hey - 我发现了 1 个问题
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="src-tauri/src/commands/download_core.rs" line_range="506-248" />
<code_context>
+ .to_ascii_lowercase()
+}
+
+fn verify_sha256(path: &Path, expected: &str) -> Result<(), String> {
+ if expected.len() != 64 || !expected.chars().all(|c| c.is_ascii_hexdigit()) {
+ return Err("更新包 SHA-256 格式无效".to_string());
+ }
+ let file = File::open(path).map_err(|e| format!("无法读取下载文件: {}", e))?;
+ let mut reader = BufReader::new(file);
+ let mut hasher = Sha256::new();
+ let mut buffer = [0u8; 64 * 1024];
+ loop {
+ let read = reader
+ .read(&mut buffer)
+ .map_err(|e| format!("无法校验下载文件: {}", e))?;
+ if read == 0 {
+ break;
+ }
+ hasher.update(&buffer[..read]);
+ }
+ let actual = format!("{:x}", hasher.finalize());
+ if actual != expected {
+ return Err(format!(
+ "更新包 SHA-256 校验失败: 预期 {},实际 {}",
+ expected, actual
+ ));
+ }
+ Ok(())
+}
+
</code_context>
<issue_to_address>
**suggestion (performance):** 考虑将 SHA-256 校验卸载到阻塞线程中,以避免在处理大文件时占用异步执行器
`verify_sha256` 会在调用线程(很可能是 Tokio 的 worker 线程)中同步执行完整的缓冲读取和哈希计算。在大体积安装包上,这可能会长期占用某个异步 worker,从而为其他任务带来额外延迟。
建议将校验逻辑包装在 `tokio::task::spawn_blocking`(或等效方案)中,并对结果执行 `await`,这样可以让这段 CPU 密集型的哈希计算运行在阻塞线程池上,同时保持当前的完整性校验逻辑不变。
建议实现如下:
```rust
fn normalize_sha256(value: &str) -> String {
value
.trim()
.strip_prefix("sha256:")
.unwrap_or(value.trim())
.to_ascii_lowercase()
}
fn verify_sha256_sync(path: &Path, expected: &str) -> Result<(), String> {
if expected.len() != 64 || !expected.chars().all(|c| c.is_ascii_hexdigit()) {
return Err("更新包 SHA-256 格式无效".to_string());
}
let file = File::open(path).map_err(|e| format!("无法读取下载文件: {}", e))?;
let mut reader = BufReader::new(file);
let mut hasher = Sha256::new();
let mut buffer = [0u8; 64 * 1024];
loop {
let read = reader
.read(&mut buffer)
.map_err(|e| format!("无法校验下载文件: {}", e))?;
if read == 0 {
break;
}
hasher.update(&buffer[..read]);
}
let actual = format!("{:x}", hasher.finalize());
if actual != expected {
return Err(format!(
"更新包 SHA-256 校验失败: 预期 {},实际 {}",
expected, actual
));
}
Ok(())
}
async fn verify_sha256(path: &Path, expected: &str) -> Result<(), String> {
let path = path.to_path_buf();
let expected = expected.to_string();
tokio::task::spawn_blocking(move || verify_sha256_sync(&path, &expected))
.await
.map_err(|e| format!("SHA-256 校验任务失败: {}", e))?
}
use tokio::task;
use super::update::move_to_old_folder;
```
为了完全采用非阻塞行为,你还需要:
1. 将所有调用 `verify_sha256` 的地方更新为对该异步函数执行 `await`,例如:
- 在异步函数中,把 `verify_sha256(path, expected_sha)?;` 修改为 `verify_sha256(path, expected_sha).await?;`。
2. 确保所有调用 `verify_sha256` 的函数本身是 `async`(或能够执行 `.await`),并让它们的调用者相应地传播 `async`/`await`。
3. 如果你不希望直接暴露一个 `async fn verify_sha256`,也可以在调用点直接使用 `tokio::task::spawn_blocking`,但上述模式能够在一个地方集中管理该行为。
</issue_to_address>帮我变得更有用!请在每条评论上点击 👍 或 👎,我会根据你的反馈改进后续评审。
Original comment in English
Hey - I've found 1 issue
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="src-tauri/src/commands/download_core.rs" line_range="506-248" />
<code_context>
+ .to_ascii_lowercase()
+}
+
+fn verify_sha256(path: &Path, expected: &str) -> Result<(), String> {
+ if expected.len() != 64 || !expected.chars().all(|c| c.is_ascii_hexdigit()) {
+ return Err("更新包 SHA-256 格式无效".to_string());
+ }
+ let file = File::open(path).map_err(|e| format!("无法读取下载文件: {}", e))?;
+ let mut reader = BufReader::new(file);
+ let mut hasher = Sha256::new();
+ let mut buffer = [0u8; 64 * 1024];
+ loop {
+ let read = reader
+ .read(&mut buffer)
+ .map_err(|e| format!("无法校验下载文件: {}", e))?;
+ if read == 0 {
+ break;
+ }
+ hasher.update(&buffer[..read]);
+ }
+ let actual = format!("{:x}", hasher.finalize());
+ if actual != expected {
+ return Err(format!(
+ "更新包 SHA-256 校验失败: 预期 {},实际 {}",
+ expected, actual
+ ));
+ }
+ Ok(())
+}
+
</code_context>
<issue_to_address>
**suggestion (performance):** Consider offloading SHA-256 verification to a blocking thread to avoid tying up async executors on large files
`verify_sha256` runs a full buffered read and hash inline on the caller thread (likely a Tokio worker). On large packages this can monopolize an async worker and introduce latency for other tasks.
Consider wrapping the verification in `tokio::task::spawn_blocking` (or equivalent) and awaiting the result so the CPU-bound hashing runs on the blocking pool while preserving the current integrity checks.
Suggested implementation:
```rust
fn normalize_sha256(value: &str) -> String {
value
.trim()
.strip_prefix("sha256:")
.unwrap_or(value.trim())
.to_ascii_lowercase()
}
fn verify_sha256_sync(path: &Path, expected: &str) -> Result<(), String> {
if expected.len() != 64 || !expected.chars().all(|c| c.is_ascii_hexdigit()) {
return Err("更新包 SHA-256 格式无效".to_string());
}
let file = File::open(path).map_err(|e| format!("无法读取下载文件: {}", e))?;
let mut reader = BufReader::new(file);
let mut hasher = Sha256::new();
let mut buffer = [0u8; 64 * 1024];
loop {
let read = reader
.read(&mut buffer)
.map_err(|e| format!("无法校验下载文件: {}", e))?;
if read == 0 {
break;
}
hasher.update(&buffer[..read]);
}
let actual = format!("{:x}", hasher.finalize());
if actual != expected {
return Err(format!(
"更新包 SHA-256 校验失败: 预期 {},实际 {}",
expected, actual
));
}
Ok(())
}
async fn verify_sha256(path: &Path, expected: &str) -> Result<(), String> {
let path = path.to_path_buf();
let expected = expected.to_string();
tokio::task::spawn_blocking(move || verify_sha256_sync(&path, &expected))
.await
.map_err(|e| format!("SHA-256 校验任务失败: {}", e))?
}
use tokio::task;
use super::update::move_to_old_folder;
```
To fully adopt the non-blocking behavior, you will also need to:
1. Update all call sites of `verify_sha256` to `await` the async function, e.g.:
- Change `verify_sha256(path, expected_sha)?;` to `verify_sha256(path, expected_sha).await?;` within async functions.
2. Ensure any functions calling `verify_sha256` are `async` (or otherwise can `.await`), and that their callers propagate `async`/`await` as needed.
3. If you prefer not to expose an `async fn verify_sha256`, you can instead call `tokio::task::spawn_blocking` directly at the call sites, but the pattern above centralizes the behavior.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
为更新包下载增加安全的断点续传能力。此前下载使用带 session ID 的临时文件,并在超时、断网等异常路径中删除半成品,用户重新下载时只能从头开始。
本 PR 将可续传更新包写入稳定的
.downloading文件,并使用元数据校验更新包身份和服务端验证器;网络失败后再次下载或重启应用均可从已完成位置继续。用户主动取消仍会清理半成品。Changes
resumeKey、预期大小、SHA-256、ETag/Last-Modified 和最终文件名。Range与If-Range,严格校验206 Content-Range;服务器忽略 Range、验证器变化或返回异常 Range 时安全回退到完整下载。416 Range Not Satisfiable,仅在本地半包大小等于远端总大小时直接完成。Testing
cargo fmt --manifest-path src-tauri/Cargo.toml -- --checkcargo test --manifest-path src-tauri/Cargo.toml(5 tests passed)cargo check --manifest-path src-tauri/Cargo.tomlpnpm format:checkpnpm buildSummary by Sourcery
添加可恢复的更新包下载功能,通过元数据验证的部分文件以及在 Tauri 后端与前端之间共享的集中式核心下载逻辑。
New Features:
.downloading文件(通过恢复标识符进行键控)实现可恢复的更新包下载,并通过文件大小、HTTP 校验字段以及可选的 SHA-256 进行验证。resumeKey和可选的 SHA-256 从更新检查结果传递到后端下载调用,以实现对部分更新包的安全复用。Bug Fixes:
Enhancements:
download_core模块,用于管理 Range/If-Range 逻辑、Content-Range验证、部分文件兼容性检查以及原子化的元数据写入。.downloading文件,以保持缓存目录整洁。Tests:
download_core模块添加类似集成测试,用于覆盖中断传输、Range 回退行为、校验字段变化、416 处理、校验和失败以及主动取消后的清理等场景。Original summary in English
Summary by Sourcery
Add resumable update package downloading with metadata-validated partial files and centralized core download logic shared by the Tauri backend and frontend.
New Features:
Bug Fixes:
Enhancements:
Tests:
Original summary in English
Summary by Sourcery
添加可恢复的更新包下载功能,通过元数据验证的部分文件以及在 Tauri 后端与前端之间共享的集中式核心下载逻辑。
New Features:
.downloading文件(通过恢复标识符进行键控)实现可恢复的更新包下载,并通过文件大小、HTTP 校验字段以及可选的 SHA-256 进行验证。resumeKey和可选的 SHA-256 从更新检查结果传递到后端下载调用,以实现对部分更新包的安全复用。Bug Fixes:
Enhancements:
download_core模块,用于管理 Range/If-Range 逻辑、Content-Range验证、部分文件兼容性检查以及原子化的元数据写入。.downloading文件,以保持缓存目录整洁。Tests:
download_core模块添加类似集成测试,用于覆盖中断传输、Range 回退行为、校验字段变化、416 处理、校验和失败以及主动取消后的清理等场景。Original summary in English
Summary by Sourcery
Add resumable update package downloading with metadata-validated partial files and centralized core download logic shared by the Tauri backend and frontend.
New Features:
Bug Fixes:
Enhancements:
Tests: