aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/src-tauri/src/core
diff options
context:
space:
mode:
Diffstat (limited to 'src-tauri/src/core')
-rw-r--r--src-tauri/src/core/downloader.rs133
-rw-r--r--src-tauri/src/core/game_version.rs66
-rw-r--r--src-tauri/src/core/mod.rs1
3 files changed, 172 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(())
}
diff --git a/src-tauri/src/core/game_version.rs b/src-tauri/src/core/game_version.rs
new file mode 100644
index 0000000..c33f99c
--- /dev/null
+++ b/src-tauri/src/core/game_version.rs
@@ -0,0 +1,66 @@
+use serde::Deserialize;
+
+#[derive(Debug, Deserialize)]
+pub struct GameVersion {
+ pub id: String,
+ pub downloads: Downloads,
+ #[serde(rename = "assetIndex")]
+ pub asset_index: AssetIndex,
+ pub libraries: Vec<Library>,
+ #[serde(rename = "mainClass")]
+ pub main_class: String,
+ #[serde(rename = "minecraftArguments")]
+ pub minecraft_arguments: Option<String>,
+ pub arguments: Option<Arguments>,
+ #[serde(rename = "javaVersion")]
+ pub java_version: Option<JavaVersion>,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct Downloads {
+ pub client: DownloadArtifact,
+ pub server: Option<DownloadArtifact>,
+}
+
+#[derive(Debug, Deserialize, Clone)]
+pub struct DownloadArtifact {
+ pub sha1: String,
+ pub size: u64,
+ pub url: String,
+ pub path: Option<String>,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct AssetIndex {
+ pub id: String,
+ pub sha1: String,
+ pub size: u64,
+ pub url: String,
+ #[serde(rename = "totalSize")]
+ pub total_size: u64,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct Library {
+ pub downloads: Option<LibraryDownloads>,
+ pub name: String,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct LibraryDownloads {
+ pub artifact: Option<DownloadArtifact>,
+ pub classifiers: Option<serde_json::Value>, // Complex, simplifying for now
+}
+
+#[derive(Debug, Deserialize)]
+pub struct Arguments {
+ pub game: Option<serde_json::Value>,
+ pub jvm: Option<serde_json::Value>,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct JavaVersion {
+ pub component: String,
+ #[serde(rename = "majorVersion")]
+ pub major_version: u64,
+}
diff --git a/src-tauri/src/core/mod.rs b/src-tauri/src/core/mod.rs
index 320ab82..aaa14a6 100644
--- a/src-tauri/src/core/mod.rs
+++ b/src-tauri/src/core/mod.rs
@@ -1,3 +1,4 @@
pub mod manifest;
pub mod auth;
pub mod downloader;
+pub mod game_version;