summaryrefslogtreecommitdiffstatshomepage
path: root/src-tauri
diff options
context:
space:
mode:
author简律纯 <i@jyunko.cn>2026-01-14 14:24:25 +0800
committerGitHub <noreply@github.com>2026-01-14 14:24:25 +0800
commit80bd692e633002e1920c9ddae7cabc7ec2eadb6a (patch)
tree36d59d2fcd87d118c6db443c9208bf34b1025eff /src-tauri
parente8e139c07d05e2f29f04906019dff5f3c520f8cc (diff)
parent561469b5a895d7c99fe6c9e73266b49ebe4237b8 (diff)
downloadDropOut-80bd692e633002e1920c9ddae7cabc7ec2eadb6a.tar.gz
DropOut-80bd692e633002e1920c9ddae7cabc7ec2eadb6a.zip
Merge pull request #20 from HsiangNianian/dev
Diffstat (limited to 'src-tauri')
-rw-r--r--src-tauri/src/core/config.rs3
-rw-r--r--src-tauri/src/core/downloader.rs142
-rw-r--r--src-tauri/src/main.rs4
3 files changed, 106 insertions, 43 deletions
diff --git a/src-tauri/src/core/config.rs b/src-tauri/src/core/config.rs
index 47c5306..d6d594f 100644
--- a/src-tauri/src/core/config.rs
+++ b/src-tauri/src/core/config.rs
@@ -5,12 +5,14 @@ use std::sync::Mutex;
use tauri::{AppHandle, Manager};
#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(default)]
pub struct LauncherConfig {
pub min_memory: u32, // in MB
pub max_memory: u32, // in MB
pub java_path: String,
pub width: u32,
pub height: u32,
+ pub download_threads: u32, // concurrent download threads (1-128)
}
impl Default for LauncherConfig {
@@ -21,6 +23,7 @@ impl Default for LauncherConfig {
java_path: "java".to_string(),
width: 854,
height: 480,
+ download_threads: 32,
}
}
}
diff --git a/src-tauri/src/core/downloader.rs b/src-tauri/src/core/downloader.rs
index 5f6ec80..7ff81ad 100644
--- a/src-tauri/src/core/downloader.rs
+++ b/src-tauri/src/core/downloader.rs
@@ -1,6 +1,7 @@
use futures::StreamExt;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
+use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::sync::Arc;
use tauri::{Emitter, Window};
use tokio::io::AsyncWriteExt;
@@ -19,11 +20,94 @@ pub struct ProgressEvent {
pub downloaded: u64,
pub total: u64,
pub status: String, // "Downloading", "Verifying", "Finished", "Error"
+ pub completed_files: usize,
+ pub total_files: usize,
+ pub total_downloaded_bytes: u64,
}
-pub async fn download_files(window: Window, tasks: Vec<DownloadTask>) -> Result<(), String> {
+/// Snapshot of global progress state
+struct ProgressSnapshot {
+ completed_files: usize,
+ total_files: usize,
+ total_downloaded_bytes: u64,
+}
+
+/// Centralized progress tracking with atomic counters
+struct GlobalProgress {
+ completed_files: AtomicUsize,
+ total_downloaded_bytes: AtomicU64,
+ total_files: usize,
+}
+
+impl GlobalProgress {
+ fn new(total_files: usize) -> Self {
+ Self {
+ completed_files: AtomicUsize::new(0),
+ total_downloaded_bytes: AtomicU64::new(0),
+ total_files,
+ }
+ }
+
+ /// Get current progress snapshot without modification
+ fn snapshot(&self) -> ProgressSnapshot {
+ ProgressSnapshot {
+ completed_files: self.completed_files.load(Ordering::Relaxed),
+ total_files: self.total_files,
+ total_downloaded_bytes: self.total_downloaded_bytes.load(Ordering::Relaxed),
+ }
+ }
+
+ /// Increment completed files counter and return updated snapshot
+ fn inc_completed(&self) -> ProgressSnapshot {
+ let completed = self.completed_files.fetch_add(1, Ordering::Relaxed) + 1;
+ ProgressSnapshot {
+ completed_files: completed,
+ total_files: self.total_files,
+ total_downloaded_bytes: self.total_downloaded_bytes.load(Ordering::Relaxed),
+ }
+ }
+
+ /// Add downloaded bytes and return updated snapshot
+ fn add_bytes(&self, delta: u64) -> ProgressSnapshot {
+ let total_bytes = self.total_downloaded_bytes.fetch_add(delta, Ordering::Relaxed) + delta;
+ ProgressSnapshot {
+ completed_files: self.completed_files.load(Ordering::Relaxed),
+ total_files: self.total_files,
+ total_downloaded_bytes: total_bytes,
+ }
+ }
+}
+
+/// Emit a progress event to the frontend
+fn emit_progress(
+ window: &Window,
+ file_name: &str,
+ status: &str,
+ downloaded: u64,
+ total: u64,
+ snapshot: &ProgressSnapshot,
+) {
+ let _ = window.emit(
+ "download-progress",
+ ProgressEvent {
+ file: file_name.to_string(),
+ downloaded,
+ total,
+ status: status.into(),
+ completed_files: snapshot.completed_files,
+ total_files: snapshot.total_files,
+ total_downloaded_bytes: snapshot.total_downloaded_bytes,
+ },
+ );
+}
+
+pub async fn download_files(window: Window, tasks: Vec<DownloadTask>, max_concurrent: usize) -> Result<(), String> {
+ // Clamp max_concurrent to a valid range (1-128) to prevent edge cases
+ let max_concurrent = max_concurrent.clamp(1, 128);
+
let client = reqwest::Client::new();
- let semaphore = Arc::new(Semaphore::new(10)); // Max 10 concurrent downloads
+ let semaphore = Arc::new(Semaphore::new(max_concurrent));
+ let progress = Arc::new(GlobalProgress::new(tasks.len()));
// Notify start (total files)
let _ = window.emit("download-start", tasks.len());
@@ -32,6 +116,7 @@ pub async fn download_files(window: Window, tasks: Vec<DownloadTask>) -> Result<
let client = client.clone();
let window = window.clone();
let semaphore = semaphore.clone();
+ let progress = progress.clone();
async move {
let _permit = semaphore.acquire().await.unwrap();
@@ -39,15 +124,7 @@ pub async fn download_files(window: Window, tasks: Vec<DownloadTask>) -> Result<
// 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(),
- },
- );
+ emit_progress(&window, &file_name, "Verifying", 0, 0, &progress.snapshot());
if let Some(expected_sha1) = &task.sha1 {
if let Ok(data) = tokio::fs::read(&task.path).await {
@@ -56,16 +133,15 @@ pub async fn download_files(window: Window, tasks: Vec<DownloadTask>) -> Result<
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(),
- },
- );
+ // Already valid, skip download
+ let skipped_size = tokio::fs::metadata(&task.path)
+ .await
+ .map(|m| m.len())
+ .unwrap_or(0);
+ if skipped_size > 0 {
+ let _ = progress.add_bytes(skipped_size);
+ }
+ emit_progress(&window, &file_name, "Skipped", 0, 0, &progress.inc_completed());
return Ok(());
}
}
@@ -93,15 +169,8 @@ pub async fn download_files(window: Window, tasks: Vec<DownloadTask>) -> Result<
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(),
- },
- );
+ let snapshot = progress.add_bytes(chunk.len() as u64);
+ emit_progress(&window, &file_name, "Downloading", downloaded, total_size, &snapshot);
}
Ok(None) => break,
Err(e) => return Err(format!("Download error: {}", e)),
@@ -111,23 +180,14 @@ pub async fn download_files(window: Window, tasks: Vec<DownloadTask>) -> Result<
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(),
- },
- );
-
+ emit_progress(&window, &file_name, "Finished", 0, 0, &progress.inc_completed());
Ok(())
}
});
// Buffer unordered to run concurrently
tasks_stream
- .buffer_unordered(10)
+ .buffer_unordered(max_concurrent)
.collect::<Vec<Result<(), String>>>()
.await;
diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs
index d7ae9a4..73310d5 100644
--- a/src-tauri/src/main.rs
+++ b/src-tauri/src/main.rs
@@ -262,8 +262,8 @@ async fn start_game(
));
// 4. Start Download
- emit_log!(window, "Starting downloads...".to_string());
- core::downloader::download_files(window.clone(), download_tasks)
+ emit_log!(window, format!("Starting downloads with {} concurrent threads...", config.download_threads));
+ core::downloader::download_files(window.clone(), download_tasks, config.download_threads as usize)
.await
.map_err(|e| e.to_string())?;
emit_log!(window, "All downloads completed successfully".to_string());