diff --git a/src-tauri/src/downloads/download_manager.rs b/src-tauri/src/downloads/download_agent.rs similarity index 59% rename from src-tauri/src/downloads/download_manager.rs rename to src-tauri/src/downloads/download_agent.rs index 4452125..be63f03 100644 --- a/src-tauri/src/downloads/download_manager.rs +++ b/src-tauri/src/downloads/download_agent.rs @@ -11,15 +11,13 @@ use std::fs::{create_dir_all, File}; use std::path::Path; use std::sync::atomic::AtomicUsize; use std::sync::{Arc, Mutex}; -use std::time::Instant; -#[derive(Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct GameDownloadManager { +pub struct GameDownloadAgent { id: String, version: String, - progress: Arc, state: Mutex, + contexts: Mutex>, + progress: ProgressChecker, pub manifest: Mutex>, } #[derive(Serialize, Deserialize, Clone, Eq, PartialEq)] @@ -37,6 +35,7 @@ pub enum GameDownloadState { #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] pub enum GameDownloadError { ManifestDownload, + FailedContextGeneration, Status(u16), System(SystemError), } @@ -49,14 +48,18 @@ pub enum SystemError { pub struct GameChunkCtx { chunk_id: usize, } -impl GameDownloadManager { +impl GameDownloadAgent { pub fn new(id: String, version: String) -> Self { Self { id, version, - progress: Arc::new(AtomicUsize::new(0)), state: Mutex::from(GameDownloadState::Uninitialised), manifest: Mutex::new(None), + progress: ProgressChecker::new( + Box::new(download_logic::download_game_chunk), + Arc::new(AtomicUsize::new(0)), + ), + contexts: Mutex::new(Vec::new()), } } pub async fn queue(&self) -> Result<(), GameDownloadError> { @@ -67,16 +70,12 @@ impl GameDownloadManager { self.ensure_manifest_exists().await } - pub fn begin_download( - &self, - max_threads: usize, - contexts: Vec, - ) -> Result<(), GameDownloadError> { - let progress = Arc::new(AtomicUsize::new(0)); + pub fn begin_download(&self, max_threads: usize) -> Result<(), GameDownloadError> { self.change_state(GameDownloadState::Downloading); - let progress = - ProgressChecker::new(Box::new(download_logic::download_game_chunk), progress); - progress.run_contexts_parallel(contexts, max_threads); + // TODO we're coping the whole context thing + // It's not necessary, I just can't figure out to make the borrow checker happy + self.progress + .run_contexts_parallel(self.contexts.lock().unwrap().to_vec(), max_threads); Ok(()) } @@ -130,41 +129,51 @@ impl GameDownloadManager { let mut lock = self.state.lock().unwrap(); *lock = state; } -} -pub fn generate_job_contexts( - manifest: &DropManifest, - version: String, - game_id: String, -) -> Vec { - let mut contexts = Vec::new(); - let base_path = DATA_ROOT_DIR.join("games").join(game_id.clone()).clone(); - create_dir_all(base_path.clone()).unwrap(); - info!("Generating contexts"); - for (raw_path, chunk) in manifest { - let path = base_path.join(Path::new(raw_path)); - let container = path.parent().unwrap(); - create_dir_all(container).unwrap(); + pub fn generate_job_contexts( + &self, + manifest: &DropManifest, + version: String, + game_id: String, + ) -> Result<(), GameDownloadError> { + let mut contexts = Vec::new(); + let base_path = DATA_ROOT_DIR.join("games").join(game_id.clone()).clone(); + create_dir_all(base_path.clone()).unwrap(); + info!("Generating contexts"); + for (raw_path, chunk) in manifest { + let path = base_path.join(Path::new(raw_path)); - let file = File::create(path.clone()).unwrap(); - let mut running_offset = 0; + let container = path.parent().unwrap(); + create_dir_all(container).unwrap(); - for (i, length) in chunk.lengths.iter().enumerate() { - contexts.push(DropDownloadContext { - file_name: raw_path.to_string(), - version: version.to_string(), - offset: running_offset, - index: i, - game_id: game_id.to_string(), - path: path.clone(), - }); - running_offset += *length as u64; + let file = File::create(path.clone()).unwrap(); + let mut running_offset = 0; + + for (i, length) in chunk.lengths.iter().enumerate() { + contexts.push(DropDownloadContext { + file_name: raw_path.to_string(), + version: version.to_string(), + offset: running_offset, + index: i, + game_id: game_id.to_string(), + path: path.clone(), + }); + running_offset += *length as u64; + } + + if running_offset > 0 { + fallocate(file, FallocateFlags::empty(), 0, running_offset).unwrap(); + } + } + info!("Finished generating"); + if let Ok(mut context_lock) = self.contexts.lock() { + *context_lock = contexts; + } else { + return Err(GameDownloadError::FailedContextGeneration); } - fallocate(file, FallocateFlags::empty(), 0, running_offset).unwrap(); + Ok(()) } - info!("Finished generating"); - contexts } #[tauri::command] @@ -176,22 +185,18 @@ pub async fn start_game_download( ) -> Result<(), GameDownloadError> { info!("Triggered Game Download"); - let download_manager = Arc::new(GameDownloadManager::new( - game_id.clone(), - game_version.clone(), - )); + let download_agent = GameDownloadAgent::new(game_id.clone(), game_version.clone()); - download_manager.ensure_manifest_exists().await?; + download_agent.ensure_manifest_exists().await?; let local_manifest = { - let manifest = download_manager.manifest.lock().unwrap(); + let manifest = download_agent.manifest.lock().unwrap(); (*manifest).clone().unwrap() }; - let contexts = generate_job_contexts(&local_manifest, game_version.clone(), game_id); + download_agent.generate_job_contexts(&local_manifest, game_version.clone(), game_id).unwrap(); - download_manager - .begin_download(max_threads, contexts)?; + download_agent.begin_download(max_threads)?; Ok(()) } diff --git a/src-tauri/src/downloads/manifest.rs b/src-tauri/src/downloads/manifest.rs index f03a706..88f365e 100644 --- a/src-tauri/src/downloads/manifest.rs +++ b/src-tauri/src/downloads/manifest.rs @@ -1,8 +1,8 @@ +use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fs::File; use std::path::PathBuf; use std::sync::{Arc, Mutex}; -use serde::{Deserialize, Serialize}; pub type DropManifest = HashMap; #[derive(Serialize, Deserialize, Debug, Clone, Ord, PartialOrd, Eq, PartialEq)] @@ -13,12 +13,12 @@ pub struct DropChunk { pub lengths: Vec, } -#[derive(Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct DropDownloadContext { pub file_name: String, pub version: String, pub index: usize, pub offset: u64, pub game_id: String, - pub path: PathBuf -} \ No newline at end of file + pub path: PathBuf, +} diff --git a/src-tauri/src/downloads/mod.rs b/src-tauri/src/downloads/mod.rs index a3129e7..d7fbe63 100644 --- a/src-tauri/src/downloads/mod.rs +++ b/src-tauri/src/downloads/mod.rs @@ -1,4 +1,4 @@ mod manifest; pub mod progress; -pub mod download_manager; +pub mod download_agent; mod download_logic; \ No newline at end of file diff --git a/src-tauri/src/downloads/progress.rs b/src-tauri/src/downloads/progress.rs index cd36303..34dc16e 100644 --- a/src-tauri/src/downloads/progress.rs +++ b/src-tauri/src/downloads/progress.rs @@ -1,21 +1,26 @@ -use std::sync::Arc; -use std::sync::atomic::{AtomicUsize, Ordering}; use rayon::ThreadPoolBuilder; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; pub struct ProgressChecker -where T: 'static + Send + Sync +where + T: 'static + Send + Sync, { counter: Arc, f: Arc>, } impl ProgressChecker -where T: Send + Sync +where + T: Send + Sync, { - pub fn new(f: Box, counter_reference: Arc) -> Self { + pub fn new( + f: Box, + counter_reference: Arc, + ) -> Self { Self { f: f.into(), - counter: counter_reference + counter: counter_reference, } } pub async fn run_contexts_sequentially_async(&self, contexts: Vec) { @@ -50,4 +55,4 @@ where T: Send + Sync pub fn get_progress_percentage>(&self, capacity: C) -> f64 { (self.get_progress() as f64) / (capacity.into()) } -} \ No newline at end of file +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index a793c0f..5bed827 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -22,7 +22,7 @@ use std::{ use std::sync::Arc; use tauri_plugin_deep_link::DeepLinkExt; use crate::db::DatabaseImpls; -use crate::downloads::download_manager::{start_game_download, GameDownloadManager}; +use crate::downloads::download_agent::{start_game_download, GameDownloadAgent}; #[derive(Clone, Copy, Serialize)] pub enum AppStatus { @@ -47,7 +47,9 @@ pub struct AppState { status: AppStatus, user: Option, games: HashMap, - game_downloads: Vec> + + #[serde(skip_serializing)] + game_downloads: Vec> } #[tauri::command]