aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/src-tauri/src/core/downloader.rs
diff options
context:
space:
mode:
authorHsiangNianian <i@jyunko.cn>2026-01-13 15:56:20 +0800
committerHsiangNianian <i@jyunko.cn>2026-01-13 15:56:20 +0800
commit66f7825ed9638606665b9e61c6f8132de013da14 (patch)
tree764c30309a1c5a77f3b0d92d5131b1b50ae50402 /src-tauri/src/core/downloader.rs
parent6fbb5f19cab02f3a0f18cdeda3da02e717b69cd6 (diff)
downloadDropOut-66f7825ed9638606665b9e61c6f8132de013da14.tar.gz
DropOut-66f7825ed9638606665b9e61c6f8132de013da14.zip
feat: implement download functionality with progress monitoring and version management
Diffstat (limited to 'src-tauri/src/core/downloader.rs')
-rw-r--r--src-tauri/src/core/downloader.rs133
1 files changed, 105 insertions, 28 deletions
diff --git a/src-tauri/src/core/downloader.rs b/src-tauri/src/core/downloader.rs
index 8d717be..0ba9aec 100644
--- a/src-tauri/src/core/downloader.rs
+++ b/src-tauri/src/core/downloader.rs
@@ -1,8 +1,12 @@
use std::path::PathBuf;
-use tokio::sync::mpsc;
use serde::{Serialize, Deserialize};
+use tauri::{Emitter, Window};
+use futures::StreamExt;
+use tokio::io::AsyncWriteExt;
+use std::sync::Arc;
+use tokio::sync::Semaphore;
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DownloadTask {
pub url: String,
pub path: PathBuf,
@@ -10,42 +14,115 @@ pub struct DownloadTask {
}
#[derive(Debug, Clone, Serialize, Deserialize)]
-pub enum DownloadProgress {
- Started(String),
- Progress { file: String, downloaded: u64, total: u64 },
- Finished(String),
- Error(String, String),
-}
-
-pub struct Downloader {
- sender: mpsc::Sender<DownloadProgress>,
+pub struct ProgressEvent {
+ pub file: String,
+ pub downloaded: u64,
+ pub total: u64,
+ pub status: String, // "Downloading", "Verifying", "Finished", "Error"
}
-impl Downloader {
- pub fn new(sender: mpsc::Sender<DownloadProgress>) -> Self {
- Self { sender }
- }
+pub async fn download_files(window: Window, tasks: Vec<DownloadTask>) -> Result<(), String> {
+ let client = reqwest::Client::new();
+ let semaphore = Arc::new(Semaphore::new(10)); // Max 10 concurrent downloads
+
+ // Notify start (total files)
+ let _ = window.emit("download-start", tasks.len());
- pub async fn download(&self, tasks: Vec<DownloadTask>) {
- // TODO: Implement parallel download with limits
- // Use futures::stream::StreamExt::buffer_unordered
+ let tasks_stream = futures::stream::iter(tasks).map(|task| {
+ let client = client.clone();
+ let window = window.clone();
+ let semaphore = semaphore.clone();
- for task in tasks {
- if let Err(_) = self.sender.send(DownloadProgress::Started(task.url.clone())).await {
- break;
+ async move {
+ let _permit = semaphore.acquire().await.unwrap();
+ let file_name = task.path.file_name().unwrap().to_string_lossy().to_string();
+
+ // 1. Check if file exists and verify SHA1
+ if task.path.exists() {
+ let _ = window.emit("download-progress", ProgressEvent {
+ file: file_name.clone(),
+ downloaded: 0,
+ total: 0,
+ status: "Verifying".into(),
+ });
+
+ if let Some(expected_sha1) = &task.sha1 {
+ if let Ok(data) = tokio::fs::read(&task.path).await {
+ let mut hasher = sha1::Sha1::new();
+ use sha1::Digest;
+ hasher.update(&data);
+ let result = hex::encode(hasher.finalize());
+ if &result == expected_sha1 {
+ // Already valid
+ let _ = window.emit("download-progress", ProgressEvent {
+ file: file_name.clone(),
+ downloaded: 0,
+ total: 0,
+ status: "Skipped".into(),
+ });
+ return Ok(());
+ }
+ }
+ }
}
- // Simulate download for now or implement basic
- // Ensure directory exists
+ // 2. Download
if let Some(parent) = task.path.parent() {
let _ = tokio::fs::create_dir_all(parent).await;
}
- // Real implementation would use reqwest here
-
- if let Err(_) = self.sender.send(DownloadProgress::Finished(task.url)).await {
- break;
+ match client.get(&task.url).send().await {
+ Ok(resp) => {
+ let total_size = resp.content_length().unwrap_or(0);
+ let mut file = match tokio::fs::File::create(&task.path).await {
+ Ok(f) => f,
+ Err(e) => return Err(format!("Create file error: {}", e)),
+ };
+
+ // reqwest::Response::bytes_stream() is only available if the 'stream' feature is enabled
+ // But we used 'blocking' and 'json'. We should add 'stream' feature to Cargo.toml?
+ // Or just use chunk().
+ // Actually, let's just create a loop if stream feature is missing or use chunk() manually if blocking is used?
+ // Wait, we are in async context. 'reqwest' dependency in Cargo.toml has 'json', 'blocking'
+ // We need 'stream' feature for .bytes_stream()
+
+ // Let's use loop with chunk()
+ loop {
+ match resp.chunk().await {
+ Ok(Some(chunk)) => {
+ if let Err(e) = file.write_all(&chunk).await {
+ return Err(format!("Write error: {}", e));
+ }
+ downloaded += chunk.len() as u64;
+ let _ = window.emit("download-progress", ProgressEvent {
+ file: file_name.clone(),
+ downloaded,
+ total: total_size,
+ status: "Downloading".into(),
+ });
+ }
+ Ok(None) => break,
+ Err(e) => return Err(format!("Download error: {}", e)),
+ }
+ }
+ },
+ Err(e) => return Err(format!("Request error: {}", e)),
}
+
+ let _ = window.emit("download-progress", ProgressEvent {
+ file: file_name.clone(),
+ downloaded: 0,
+ total: 0,
+ status: "Finished".into(),
+ });
+
+ Ok(())
}
- }
+ });
+
+ // Buffer unordered to run concurrently
+ tasks_stream.buffer_unordered(10).collect::<Vec<Result<(), String>>>().await;
+
+ let _ = window.emit("download-complete", ());
+ Ok(())
}