From 86f77daa01a55e3c98cf0ed91693ff19779d41b8 Mon Sep 17 00:00:00 2001 From: DecDuck Date: Tue, 12 Nov 2024 09:06:28 +1100 Subject: [PATCH 01/31] chore(downloads): partial download manager --- .../src-tauri/src/downloads/download_agent.rs | 9 +++-- .../src/downloads/download_commands.rs | 13 ++++-- .../src/downloads/download_manager.rs | 40 +++++++++++++++++++ desktop/src-tauri/src/downloads/mod.rs | 1 + desktop/src-tauri/src/lib.rs | 14 ++++--- 5 files changed, 66 insertions(+), 11 deletions(-) create mode 100644 desktop/src-tauri/src/downloads/download_manager.rs diff --git a/desktop/src-tauri/src/downloads/download_agent.rs b/desktop/src-tauri/src/downloads/download_agent.rs index a46eceb3..8bcd2b5b 100644 --- a/desktop/src-tauri/src/downloads/download_agent.rs +++ b/desktop/src-tauri/src/downloads/download_agent.rs @@ -4,11 +4,13 @@ use crate::downloads::manifest::{DropDownloadContext, DropManifest}; use crate::remote::RemoteAccessError; use crate::DB; use log::info; -use rayon::ThreadPoolBuilder; +use rayon::{spawn, ThreadPool, ThreadPoolBuilder}; use std::fmt::{Display, Formatter}; use std::fs::{create_dir_all, File}; use std::path::Path; -use std::sync::Mutex; +use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; +use std::sync::{Arc, Mutex}; +use std::thread::Thread; use urlencoding::encode; #[cfg(target_os = "linux")] @@ -207,6 +209,7 @@ impl GameDownloadAgent { .build() .unwrap(); + pool.scope(move |scope| { let contexts = self.contexts.lock().unwrap(); @@ -223,6 +226,6 @@ impl GameDownloadAgent { download_game_chunk(context, control_flag, progress).unwrap(); }); } - }) + }); } } diff --git a/desktop/src-tauri/src/downloads/download_commands.rs b/desktop/src-tauri/src/downloads/download_commands.rs index 54b93152..9449ab24 100644 --- a/desktop/src-tauri/src/downloads/download_commands.rs +++ b/desktop/src-tauri/src/downloads/download_commands.rs @@ -11,6 +11,7 @@ pub fn download_game( game_version: String, state: tauri::State<'_, Mutex>, ) -> Result<(), String> { + /* info!("beginning game download..."); let mut download_agent = GameDownloadAgent::new(game_id.clone(), game_version.clone(), 0); @@ -19,7 +20,7 @@ pub fn download_game( let mut lock: std::sync::MutexGuard<'_, AppState> = state.lock().unwrap(); let download_agent_ref = Arc::new(download_agent); - lock.game_downloads + lock.download_manager .insert(game_id, download_agent_ref.clone()); // Run it in another thread @@ -27,6 +28,7 @@ pub fn download_game( // Run doesn't require mutable download_agent_ref.clone().run(); }); + */ Ok(()) } @@ -36,18 +38,23 @@ pub fn get_game_download_progress( state: tauri::State<'_, Mutex>, game_id: String, ) -> Result { + /* let download_agent = use_download_agent(state, game_id)?; let progress = &download_agent.progress; Ok(progress.get_progress()) -} + */ + Ok(0.0) +} +/* fn use_download_agent( state: tauri::State<'_, Mutex>, game_id: String, ) -> Result, String> { let lock = state.lock().unwrap(); - let download_agent = lock.game_downloads.get(&game_id).ok_or("Invalid game ID")?; + let download_agent = lock.download_manager.get(&game_id).ok_or("Invalid game ID")?; Ok(download_agent.clone()) // Clones the Arc, not the underlying data structure } +*/ \ No newline at end of file diff --git a/desktop/src-tauri/src/downloads/download_manager.rs b/desktop/src-tauri/src/downloads/download_manager.rs new file mode 100644 index 00000000..3c14fa84 --- /dev/null +++ b/desktop/src-tauri/src/downloads/download_manager.rs @@ -0,0 +1,40 @@ +use std::{ + collections::HashMap, + sync::{Arc, Mutex}, + thread::JoinHandle, +}; + +use super::{download_agent::GameDownloadAgent, download_thread_control_flag::DownloadThreadControlFlag}; + +pub struct DownloadManager { + download_agent_registry: HashMap>>, + download_queue: Vec, + + current_thread: Option>, + current_game_id: Option, // Should be the only game download agent in the map with the "Go" flag +} + +impl DownloadManager { + pub fn new() -> Self { + return Self { + download_agent_registry: HashMap::new(), + download_queue: Vec::new(), + current_thread: None, + current_game_id: None, + }; + } + + pub fn queue_game(&mut self, game_id: String, version_name: String) { + let existing_da = self.download_agent_registry.get(&game_id); + + if let Some(da_mutex) = existing_da { + let da = da_mutex.lock().unwrap(); + if da.version == version_name { + return; // We're already queued + } + + da.control_flag.set(DownloadThreadControlFlag::Stop); + + } + } +} diff --git a/desktop/src-tauri/src/downloads/mod.rs b/desktop/src-tauri/src/downloads/mod.rs index d4ade96f..ea2edfdf 100644 --- a/desktop/src-tauri/src/downloads/mod.rs +++ b/desktop/src-tauri/src/downloads/mod.rs @@ -1,5 +1,6 @@ pub mod download_agent; pub mod download_commands; +pub mod download_manager; mod download_logic; mod download_thread_control_flag; mod manifest; diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs index bef2fab1..5e880a25 100644 --- a/desktop/src-tauri/src/lib.rs +++ b/desktop/src-tauri/src/lib.rs @@ -13,6 +13,7 @@ use crate::downloads::download_agent::GameDownloadAgent; use auth::{auth_initiate, generate_authorization_header, recieve_handshake}; use db::{add_new_download_dir, DatabaseInterface, DATA_ROOT_DIR}; use downloads::download_commands::*; +use downloads::download_manager::DownloadManager; use env_logger::Env; use http::{header::*, response::Builder as ResponseBuilder}; use library::{fetch_game, fetch_library, Game}; @@ -53,7 +54,7 @@ pub struct AppState { games: HashMap, #[serde(skip_serializing)] - game_downloads: HashMap>, + download_manager: Arc, } #[tauri::command] @@ -67,13 +68,16 @@ fn fetch_state(state: tauri::State<'_, Mutex>) -> Result AppState { env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); + let games = HashMap::new(); + let download_manager = Arc::new(DownloadManager::new()); + let is_set_up = DB.database_is_set_up(); if !is_set_up { return AppState { status: AppStatus::NotConfigured, user: None, - games: HashMap::new(), - game_downloads: HashMap::new(), + games, + download_manager, }; } @@ -81,8 +85,8 @@ fn setup() -> AppState { AppState { status: app_status, user, - games: HashMap::new(), - game_downloads: HashMap::new(), + games, + download_manager, } } From 928b60483a0a9447ee30482cba6f42ee26291bca Mon Sep 17 00:00:00 2001 From: quexeky Date: Wed, 13 Nov 2024 20:38:00 +1100 Subject: [PATCH 02/31] feat(downloads): Added Download Manager Signed-off-by: quexeky --- .../src-tauri/src/downloads/download_agent.rs | 12 +- .../src/downloads/download_commands.rs | 10 +- .../src-tauri/src/downloads/download_logic.rs | 2 + .../src/downloads/download_manager.rs | 196 +++++++++++++++--- desktop/src-tauri/src/downloads/mod.rs | 2 +- .../src/downloads/progress_object.rs | 25 ++- desktop/src-tauri/src/lib.rs | 6 +- 7 files changed, 206 insertions(+), 47 deletions(-) diff --git a/desktop/src-tauri/src/downloads/download_agent.rs b/desktop/src-tauri/src/downloads/download_agent.rs index 8bcd2b5b..c977ecd1 100644 --- a/desktop/src-tauri/src/downloads/download_agent.rs +++ b/desktop/src-tauri/src/downloads/download_agent.rs @@ -66,7 +66,7 @@ impl GameDownloadAgent { // Blocking // Requires mutable self - pub fn setup_download(&mut self) -> Result<(), GameDownloadError> { + pub fn setup_download(&self) -> Result<(), GameDownloadError> { self.ensure_manifest_exists()?; info!("Ensured manifest exists"); @@ -79,23 +79,22 @@ impl GameDownloadAgent { } // Blocking - pub fn download(&mut self) -> Result<(), GameDownloadError> { + pub fn download(&self) -> Result<(), GameDownloadError> { self.setup_download()?; self.run(); Ok(()) } - pub fn ensure_manifest_exists(&mut self) -> Result<(), GameDownloadError> { + pub fn ensure_manifest_exists(&self) -> Result<(), GameDownloadError> { if self.manifest.lock().unwrap().is_some() { return Ok(()); } - // Explicitly propagate error self.download_manifest() } - fn download_manifest(&mut self) -> Result<(), GameDownloadError> { + fn download_manifest(&self) -> Result<(), GameDownloadError> { let base_url = DB.fetch_base_url(); let manifest_url = base_url .join( @@ -138,7 +137,8 @@ impl GameDownloadAgent { .values() .map(|chunk| chunk.lengths.len()) .sum(); - self.progress = ProgressObject::new(length.try_into().unwrap(), chunk_count); + self.progress.set_max(length.try_into().unwrap()); + self.progress.set_size(chunk_count); if let Ok(mut manifest) = self.manifest.lock() { *manifest = Some(manifest_download); diff --git a/desktop/src-tauri/src/downloads/download_commands.rs b/desktop/src-tauri/src/downloads/download_commands.rs index 9449ab24..c1ab5411 100644 --- a/desktop/src-tauri/src/downloads/download_commands.rs +++ b/desktop/src-tauri/src/downloads/download_commands.rs @@ -3,7 +3,7 @@ use std::sync::{Arc, Mutex}; use log::info; use rayon::spawn; -use crate::{downloads::download_agent::GameDownloadAgent, AppState}; +use crate::{AppState}; #[tauri::command] pub fn download_game( @@ -29,7 +29,7 @@ pub fn download_game( download_agent_ref.clone().run(); }); */ - + state.lock().unwrap().download_manager.queue_game(game_id, game_version, 0).unwrap(); Ok(()) } @@ -45,8 +45,12 @@ pub fn get_game_download_progress( Ok(progress.get_progress()) */ + let progress = match state.lock().unwrap().download_manager.get_current_game_download_progress() { + Some(progress) => progress, + None => 0.0 + }; - Ok(0.0) + Ok(progress) } /* fn use_download_agent( diff --git a/desktop/src-tauri/src/downloads/download_logic.rs b/desktop/src-tauri/src/downloads/download_logic.rs index 2a0c21f2..dc95f2fd 100644 --- a/desktop/src-tauri/src/downloads/download_logic.rs +++ b/desktop/src-tauri/src/downloads/download_logic.rs @@ -3,6 +3,7 @@ use crate::db::DatabaseImpls; use crate::downloads::manifest::DropDownloadContext; use crate::remote::RemoteAccessError; use crate::DB; +use log::info; use md5::{Context, Digest}; use reqwest::blocking::Response; @@ -123,6 +124,7 @@ pub fn download_game_chunk( ) -> Result { // If we're paused if control_flag.get() == DownloadThreadControlFlag::Stop { + info!("Control flag is Stop"); return Ok(false); } diff --git a/desktop/src-tauri/src/downloads/download_manager.rs b/desktop/src-tauri/src/downloads/download_manager.rs index 3c14fa84..dcd7bf52 100644 --- a/desktop/src-tauri/src/downloads/download_manager.rs +++ b/desktop/src-tauri/src/downloads/download_manager.rs @@ -1,40 +1,182 @@ use std::{ - collections::HashMap, - sync::{Arc, Mutex}, - thread::JoinHandle, + collections::{HashMap, VecDeque}, sync::{mpsc::{channel, Receiver, SendError, Sender}, Arc, Mutex, MutexGuard}, thread::{spawn, JoinHandle}, }; -use super::{download_agent::GameDownloadAgent, download_thread_control_flag::DownloadThreadControlFlag}; +use log::info; + +use super::{download_agent::GameDownloadAgent, download_thread_control_flag::{DownloadThreadControl, DownloadThreadControlFlag}, progress_object::ProgressObject}; pub struct DownloadManager { - download_agent_registry: HashMap>>, - download_queue: Vec, + download_agent_registry: HashMap>, + download_queue: Arc>>, + receiver: Receiver, + sender: Sender, + progress: Arc>>, - current_thread: Option>, current_game_id: Option, // Should be the only game download agent in the map with the "Go" flag + active_control_flag: Option +} +pub struct DownloadManagerInterface { + terminator: JoinHandle>, + download_queue: Arc>>, + progress: Arc>>, + sender: Sender, +} +pub enum DownloadManagerSignal { + Go, + Stop, + Completed(String), + Queue(String, String, usize) } -impl DownloadManager { - pub fn new() -> Self { - return Self { - download_agent_registry: HashMap::new(), - download_queue: Vec::new(), - current_thread: None, - current_game_id: None, - }; +impl DownloadManagerInterface { + pub fn queue_game(&self, game_id: String, version: String, target_download_dir: usize) -> Result<(), SendError> { + info!("Adding game id {}", game_id); + self.sender.send(DownloadManagerSignal::Queue(game_id, version, target_download_dir))?; + self.sender.send(DownloadManagerSignal::Go) } - - pub fn queue_game(&mut self, game_id: String, version_name: String) { - let existing_da = self.download_agent_registry.get(&game_id); - - if let Some(da_mutex) = existing_da { - let da = da_mutex.lock().unwrap(); - if da.version == version_name { - return; // We're already queued - } - - da.control_flag.set(DownloadThreadControlFlag::Stop); - + pub fn edit(&self) -> MutexGuard<'_, VecDeque> { + self.download_queue.lock().unwrap() + } + pub fn get_current_game_download_progress(&self) -> Option { + let progress_object = (*self.progress.lock().unwrap()).clone()?; + Some(progress_object.get_progress()) + } + pub fn rearrange_string(&self, id: String, new_index: usize) { + let mut queue = self.edit(); + let current_index = get_index_from_id(&mut queue, id).unwrap(); + let to_move = queue.remove(current_index).unwrap(); + queue.insert(new_index, to_move); + } + pub fn rearrange(&self, current_index: usize, new_index: usize) { + let mut queue = self.edit(); + let to_move = queue.remove(current_index).unwrap(); + queue.insert(new_index, to_move); + } + pub fn remove_from_queue(&self, index: usize) { + self.edit().remove(index); + } + pub fn remove_from_queue_string(&self, game_id: String) { + let mut queue = self.edit(); + let current_index = get_index_from_id(&mut queue, game_id).unwrap(); + queue.remove(current_index); + } + pub fn pause_downloads(&self) -> Result<(), SendError> { + self.sender.send(DownloadManagerSignal::Stop) + } + pub fn resume_downloads(&self) -> Result<(), SendError> { + self.sender.send(DownloadManagerSignal::Go) + } + pub fn ensure_terminated(self) -> Result<(), ()> { + match self.terminator.join() { + Ok(o) => o, + Err(_) => Err(()), } } } + +impl DownloadManager { + pub fn generate() -> DownloadManagerInterface { + let queue = Arc::new(Mutex::new(VecDeque::new())); + let (sender, receiver) = channel(); + let active_progress = Arc::new(Mutex::new(None)); + + let manager = Self { + download_agent_registry: HashMap::new(), + download_queue: queue.clone(), + receiver, + current_game_id: None, + active_control_flag: None, + sender: sender.clone(), + progress: active_progress.clone(), + }; + + let terminator = spawn(|| {manager.manage_queue()}); + + let interface = DownloadManagerInterface { + terminator, + download_queue: queue, + sender, + progress: active_progress + }; + return interface; + } + + fn manage_queue(mut self) -> Result<(), ()> { + loop { + let signal = match self.receiver.recv() { + Ok(signal) => signal, + Err(e) => { + return Err(()) + }, + }; + + match signal { + DownloadManagerSignal::Go => { + info!("Got signal 'Go'"); + if self.active_control_flag.is_none() && !self.download_agent_registry.is_empty() { + info!("Starting download agent"); + let download_agent = { + let lock = self.download_queue.lock().unwrap(); + self.download_agent_registry.get(&lock.front().unwrap().clone()).unwrap().clone() + }; + self.current_game_id = Some(download_agent.id.clone()); + + let progress_object = download_agent.progress.clone(); + *self.progress.lock().unwrap() = Some(progress_object); + + let active_control_flag = download_agent.control_flag.clone(); + self.active_control_flag = Some(active_control_flag.clone()); + + let sender = self.sender.clone(); + + info!("Spawning download"); + spawn(move || { + download_agent.download().unwrap(); + sender.send(DownloadManagerSignal::Completed(download_agent.id.clone())).unwrap(); + }); + info!("Finished spawning Download"); + + active_control_flag.set(DownloadThreadControlFlag::Go); + } + else if let Some(active_control_flag) = self.active_control_flag.clone() { + info!("Restarting current download"); + active_control_flag.set(DownloadThreadControlFlag::Go); + } + else { + info!("Nothing was set"); + } + }, + DownloadManagerSignal::Stop => { + info!("Got signal 'Stop'"); + if let Some(active_control_flag) = self.active_control_flag.clone() { + active_control_flag.set(DownloadThreadControlFlag::Stop); + } + }, + DownloadManagerSignal::Completed(game_id) => { + info!("Got signal 'Completed'"); + if self.current_game_id == Some(game_id.clone()) { + info!("Popping consumed data"); + self.download_queue.lock().unwrap().pop_front(); + self.download_agent_registry.remove(&game_id); + self.active_control_flag = None; + *self.progress.lock().unwrap() = None; + } + self.sender.send(DownloadManagerSignal::Go).unwrap(); + } + DownloadManagerSignal::Queue(game_id, version, target_download_dir) => { + info!("Got signal Queue"); + let download_agent = Arc::new(GameDownloadAgent::new(game_id.clone(), version, target_download_dir)); + self.download_agent_registry.insert(game_id.clone(), download_agent); + self.download_queue.lock().unwrap().push_back(game_id); + }, + }; + } + } +} + +pub fn get_index_from_id(queue: &mut MutexGuard<'_, VecDeque>, id: String) -> Option { + queue.iter().position(|download_agent| { + download_agent == &id + }) +} diff --git a/desktop/src-tauri/src/downloads/mod.rs b/desktop/src-tauri/src/downloads/mod.rs index ea2edfdf..e166e3f9 100644 --- a/desktop/src-tauri/src/downloads/mod.rs +++ b/desktop/src-tauri/src/downloads/mod.rs @@ -4,4 +4,4 @@ pub mod download_manager; mod download_logic; mod download_thread_control_flag; mod manifest; -mod progress_object; +mod progress_object; \ No newline at end of file diff --git a/desktop/src-tauri/src/downloads/progress_object.rs b/desktop/src-tauri/src/downloads/progress_object.rs index f3be4fbd..6ed24000 100644 --- a/desktop/src-tauri/src/downloads/progress_object.rs +++ b/desktop/src-tauri/src/downloads/progress_object.rs @@ -1,33 +1,44 @@ use std::sync::{ atomic::{AtomicUsize, Ordering}, - Arc, + Arc, Mutex, }; #[derive(Clone)] pub struct ProgressObject { - max: usize, - progress_instances: Arc>>, + max: Arc>, + progress_instances: Arc>>>, } impl ProgressObject { pub fn new(max: usize, length: usize) -> Self { - let arr = (0..length).map(|_| Arc::new(AtomicUsize::new(0))).collect(); + let arr = Mutex::new((0..length).map(|_| Arc::new(AtomicUsize::new(0))).collect()); Self { - max, + max: Arc::new(Mutex::new(max)), progress_instances: Arc::new(arr), } } pub fn sum(&self) -> usize { self.progress_instances + .lock() + .unwrap() .iter() .map(|instance| instance.load(Ordering::Relaxed)) .sum() } + pub fn get_max(&self) -> usize { + self.max.lock().unwrap().clone() + } + pub fn set_max(&self, new_max: usize) { + *self.max.lock().unwrap() = new_max + } + pub fn set_size(&self, length: usize) { + *self.progress_instances.lock().unwrap() = (0..length).map(|_| Arc::new(AtomicUsize::new(0))).collect(); + } pub fn get_progress(&self) -> f64 { - self.sum() as f64 / self.max as f64 + self.sum() as f64 / self.get_max() as f64 } pub fn get(&self, index: usize) -> Arc { - self.progress_instances[index].clone() + self.progress_instances.lock().unwrap()[index].clone() } } diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs index 5e880a25..deb7a4cc 100644 --- a/desktop/src-tauri/src/lib.rs +++ b/desktop/src-tauri/src/lib.rs @@ -13,7 +13,7 @@ use crate::downloads::download_agent::GameDownloadAgent; use auth::{auth_initiate, generate_authorization_header, recieve_handshake}; use db::{add_new_download_dir, DatabaseInterface, DATA_ROOT_DIR}; use downloads::download_commands::*; -use downloads::download_manager::DownloadManager; +use downloads::download_manager::{DownloadManager, DownloadManagerInterface}; use env_logger::Env; use http::{header::*, response::Builder as ResponseBuilder}; use library::{fetch_game, fetch_library, Game}; @@ -54,7 +54,7 @@ pub struct AppState { games: HashMap, #[serde(skip_serializing)] - download_manager: Arc, + download_manager: Arc, } #[tauri::command] @@ -69,7 +69,7 @@ fn setup() -> AppState { env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); let games = HashMap::new(); - let download_manager = Arc::new(DownloadManager::new()); + let download_manager = Arc::new(DownloadManager::generate()); let is_set_up = DB.database_is_set_up(); if !is_set_up { From cee86b43edca11c8bc07d0406a869798c7d58755 Mon Sep 17 00:00:00 2001 From: quexeky Date: Wed, 13 Nov 2024 21:05:25 +1100 Subject: [PATCH 03/31] refactor(downloads): Ran cargo clippy & moved DownloadManagerInterface Created file "download_manager_interface.rs" to contain the DownloadManagerInterface Signed-off-by: quexeky --- .../src-tauri/src/downloads/download_agent.rs | 8 +- .../src/downloads/download_commands.rs | 9 +- .../src/downloads/download_manager.rs | 184 +++++++----------- .../downloads/download_manager_interface.rs | 73 +++++++ desktop/src-tauri/src/downloads/mod.rs | 1 + .../src/downloads/progress_object.rs | 2 +- desktop/src-tauri/src/lib.rs | 1 - 7 files changed, 151 insertions(+), 127 deletions(-) create mode 100644 desktop/src-tauri/src/downloads/download_manager_interface.rs diff --git a/desktop/src-tauri/src/downloads/download_agent.rs b/desktop/src-tauri/src/downloads/download_agent.rs index c977ecd1..b582c7a0 100644 --- a/desktop/src-tauri/src/downloads/download_agent.rs +++ b/desktop/src-tauri/src/downloads/download_agent.rs @@ -4,13 +4,11 @@ use crate::downloads::manifest::{DropDownloadContext, DropManifest}; use crate::remote::RemoteAccessError; use crate::DB; use log::info; -use rayon::{spawn, ThreadPool, ThreadPoolBuilder}; +use rayon::ThreadPoolBuilder; use std::fmt::{Display, Formatter}; use std::fs::{create_dir_all, File}; use std::path::Path; -use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; -use std::sync::{Arc, Mutex}; -use std::thread::Thread; +use std::sync::Mutex; use urlencoding::encode; #[cfg(target_os = "linux")] @@ -137,7 +135,7 @@ impl GameDownloadAgent { .values() .map(|chunk| chunk.lengths.len()) .sum(); - self.progress.set_max(length.try_into().unwrap()); + self.progress.set_max(length); self.progress.set_size(chunk_count); if let Ok(mut manifest) = self.manifest.lock() { diff --git a/desktop/src-tauri/src/downloads/download_commands.rs b/desktop/src-tauri/src/downloads/download_commands.rs index c1ab5411..74a1bd30 100644 --- a/desktop/src-tauri/src/downloads/download_commands.rs +++ b/desktop/src-tauri/src/downloads/download_commands.rs @@ -1,7 +1,5 @@ -use std::sync::{Arc, Mutex}; +use std::sync::Mutex; -use log::info; -use rayon::spawn; use crate::{AppState}; @@ -45,10 +43,7 @@ pub fn get_game_download_progress( Ok(progress.get_progress()) */ - let progress = match state.lock().unwrap().download_manager.get_current_game_download_progress() { - Some(progress) => progress, - None => 0.0 - }; + let progress = state.lock().unwrap().download_manager.get_current_game_download_progress().unwrap_or(0.0); Ok(progress) } diff --git a/desktop/src-tauri/src/downloads/download_manager.rs b/desktop/src-tauri/src/downloads/download_manager.rs index dcd7bf52..7d2e44b1 100644 --- a/desktop/src-tauri/src/downloads/download_manager.rs +++ b/desktop/src-tauri/src/downloads/download_manager.rs @@ -4,7 +4,7 @@ use std::{ use log::info; -use super::{download_agent::GameDownloadAgent, download_thread_control_flag::{DownloadThreadControl, DownloadThreadControlFlag}, progress_object::ProgressObject}; +use super::{download_agent::GameDownloadAgent, download_manager_interface::DownloadManagerInterface, download_thread_control_flag::{DownloadThreadControl, DownloadThreadControlFlag}, progress_object::ProgressObject}; pub struct DownloadManager { download_agent_registry: HashMap>, @@ -16,12 +16,6 @@ pub struct DownloadManager { current_game_id: Option, // Should be the only game download agent in the map with the "Go" flag active_control_flag: Option } -pub struct DownloadManagerInterface { - terminator: JoinHandle>, - download_queue: Arc>>, - progress: Arc>>, - sender: Sender, -} pub enum DownloadManagerSignal { Go, Stop, @@ -29,51 +23,6 @@ pub enum DownloadManagerSignal { Queue(String, String, usize) } -impl DownloadManagerInterface { - pub fn queue_game(&self, game_id: String, version: String, target_download_dir: usize) -> Result<(), SendError> { - info!("Adding game id {}", game_id); - self.sender.send(DownloadManagerSignal::Queue(game_id, version, target_download_dir))?; - self.sender.send(DownloadManagerSignal::Go) - } - pub fn edit(&self) -> MutexGuard<'_, VecDeque> { - self.download_queue.lock().unwrap() - } - pub fn get_current_game_download_progress(&self) -> Option { - let progress_object = (*self.progress.lock().unwrap()).clone()?; - Some(progress_object.get_progress()) - } - pub fn rearrange_string(&self, id: String, new_index: usize) { - let mut queue = self.edit(); - let current_index = get_index_from_id(&mut queue, id).unwrap(); - let to_move = queue.remove(current_index).unwrap(); - queue.insert(new_index, to_move); - } - pub fn rearrange(&self, current_index: usize, new_index: usize) { - let mut queue = self.edit(); - let to_move = queue.remove(current_index).unwrap(); - queue.insert(new_index, to_move); - } - pub fn remove_from_queue(&self, index: usize) { - self.edit().remove(index); - } - pub fn remove_from_queue_string(&self, game_id: String) { - let mut queue = self.edit(); - let current_index = get_index_from_id(&mut queue, game_id).unwrap(); - queue.remove(current_index); - } - pub fn pause_downloads(&self) -> Result<(), SendError> { - self.sender.send(DownloadManagerSignal::Stop) - } - pub fn resume_downloads(&self) -> Result<(), SendError> { - self.sender.send(DownloadManagerSignal::Go) - } - pub fn ensure_terminated(self) -> Result<(), ()> { - match self.terminator.join() { - Ok(o) => o, - Err(_) => Err(()), - } - } -} impl DownloadManager { pub fn generate() -> DownloadManagerInterface { @@ -93,13 +42,12 @@ impl DownloadManager { let terminator = spawn(|| {manager.manage_queue()}); - let interface = DownloadManagerInterface { + DownloadManagerInterface::new( terminator, - download_queue: queue, + queue, + active_progress, sender, - progress: active_progress - }; - return interface; + ) } fn manage_queue(mut self) -> Result<(), ()> { @@ -113,70 +61,80 @@ impl DownloadManager { match signal { DownloadManagerSignal::Go => { - info!("Got signal 'Go'"); - if self.active_control_flag.is_none() && !self.download_agent_registry.is_empty() { - info!("Starting download agent"); - let download_agent = { - let lock = self.download_queue.lock().unwrap(); - self.download_agent_registry.get(&lock.front().unwrap().clone()).unwrap().clone() - }; - self.current_game_id = Some(download_agent.id.clone()); - - let progress_object = download_agent.progress.clone(); - *self.progress.lock().unwrap() = Some(progress_object); - - let active_control_flag = download_agent.control_flag.clone(); - self.active_control_flag = Some(active_control_flag.clone()); - - let sender = self.sender.clone(); - - info!("Spawning download"); - spawn(move || { - download_agent.download().unwrap(); - sender.send(DownloadManagerSignal::Completed(download_agent.id.clone())).unwrap(); - }); - info!("Finished spawning Download"); - - active_control_flag.set(DownloadThreadControlFlag::Go); - } - else if let Some(active_control_flag) = self.active_control_flag.clone() { - info!("Restarting current download"); - active_control_flag.set(DownloadThreadControlFlag::Go); - } - else { - info!("Nothing was set"); - } + self.manage_go_signal(); }, DownloadManagerSignal::Stop => { - info!("Got signal 'Stop'"); - if let Some(active_control_flag) = self.active_control_flag.clone() { - active_control_flag.set(DownloadThreadControlFlag::Stop); - } + self.manage_stop_signal(); }, DownloadManagerSignal::Completed(game_id) => { - info!("Got signal 'Completed'"); - if self.current_game_id == Some(game_id.clone()) { - info!("Popping consumed data"); - self.download_queue.lock().unwrap().pop_front(); - self.download_agent_registry.remove(&game_id); - self.active_control_flag = None; - *self.progress.lock().unwrap() = None; - } - self.sender.send(DownloadManagerSignal::Go).unwrap(); + self.manage_completed_signal(game_id); } DownloadManagerSignal::Queue(game_id, version, target_download_dir) => { - info!("Got signal Queue"); - let download_agent = Arc::new(GameDownloadAgent::new(game_id.clone(), version, target_download_dir)); - self.download_agent_registry.insert(game_id.clone(), download_agent); - self.download_queue.lock().unwrap().push_back(game_id); + self.manage_queue_signal(game_id, version, target_download_dir); }, }; } } -} -pub fn get_index_from_id(queue: &mut MutexGuard<'_, VecDeque>, id: String) -> Option { - queue.iter().position(|download_agent| { - download_agent == &id - }) -} + fn manage_stop_signal(&mut self) { + info!("Got signal 'Stop'"); + if let Some(active_control_flag) = self.active_control_flag.clone() { + active_control_flag.set(DownloadThreadControlFlag::Stop); + } + } + + fn manage_completed_signal(&mut self, game_id: String) { + info!("Got signal 'Completed'"); + if self.current_game_id == Some(game_id.clone()) { + info!("Popping consumed data"); + self.download_queue.lock().unwrap().pop_front(); + self.download_agent_registry.remove(&game_id); + self.active_control_flag = None; + *self.progress.lock().unwrap() = None; + } + self.sender.send(DownloadManagerSignal::Go).unwrap(); + } + + fn manage_queue_signal(&mut self, game_id: String, version: String, target_download_dir: usize) { + info!("Got signal Queue"); + let download_agent = Arc::new(GameDownloadAgent::new(game_id.clone(), version, target_download_dir)); + self.download_agent_registry.insert(game_id.clone(), download_agent); + self.download_queue.lock().unwrap().push_back(game_id); + } + + fn manage_go_signal(&mut self) { + info!("Got signal 'Go'"); + if self.active_control_flag.is_none() && !self.download_agent_registry.is_empty() { + info!("Starting download agent"); + let download_agent = { + let lock = self.download_queue.lock().unwrap(); + self.download_agent_registry.get(&lock.front().unwrap().clone()).unwrap().clone() + }; + self.current_game_id = Some(download_agent.id.clone()); + + let progress_object = download_agent.progress.clone(); + *self.progress.lock().unwrap() = Some(progress_object); + + let active_control_flag = download_agent.control_flag.clone(); + self.active_control_flag = Some(active_control_flag.clone()); + + let sender = self.sender.clone(); + + info!("Spawning download"); + spawn(move || { + download_agent.download().unwrap(); + sender.send(DownloadManagerSignal::Completed(download_agent.id.clone())).unwrap(); + }); + info!("Finished spawning Download"); + + active_control_flag.set(DownloadThreadControlFlag::Go); + } + else if let Some(active_control_flag) = self.active_control_flag.clone() { + info!("Restarting current download"); + active_control_flag.set(DownloadThreadControlFlag::Go); + } + else { + info!("Nothing was set"); + } + } +} \ No newline at end of file diff --git a/desktop/src-tauri/src/downloads/download_manager_interface.rs b/desktop/src-tauri/src/downloads/download_manager_interface.rs new file mode 100644 index 00000000..40efdd65 --- /dev/null +++ b/desktop/src-tauri/src/downloads/download_manager_interface.rs @@ -0,0 +1,73 @@ +use std::{collections::VecDeque, sync::{mpsc::{SendError, Sender}, Arc, Mutex, MutexGuard}, thread::JoinHandle}; + +use log::info; + +use super::{download_manager::DownloadManagerSignal, progress_object::ProgressObject}; + +pub struct DownloadManagerInterface { + terminator: JoinHandle>, + download_queue: Arc>>, + progress: Arc>>, + sender: Sender, +} + +impl DownloadManagerInterface { + + pub fn new( + terminator: JoinHandle>, + download_queue: Arc>>, + progress: Arc>>, + sender: Sender) -> Self { + Self { terminator, download_queue, progress, sender } + } + + pub fn queue_game(&self, game_id: String, version: String, target_download_dir: usize) -> Result<(), SendError> { + info!("Adding game id {}", game_id); + self.sender.send(DownloadManagerSignal::Queue(game_id, version, target_download_dir))?; + self.sender.send(DownloadManagerSignal::Go) + } + pub fn edit(&self) -> MutexGuard<'_, VecDeque> { + self.download_queue.lock().unwrap() + } + pub fn get_current_game_download_progress(&self) -> Option { + let progress_object = (*self.progress.lock().unwrap()).clone()?; + Some(progress_object.get_progress()) + } + pub fn rearrange_string(&self, id: String, new_index: usize) { + let mut queue = self.edit(); + let current_index = get_index_from_id(&mut queue, id).unwrap(); + let to_move = queue.remove(current_index).unwrap(); + queue.insert(new_index, to_move); + } + pub fn rearrange(&self, current_index: usize, new_index: usize) { + let mut queue = self.edit(); + let to_move = queue.remove(current_index).unwrap(); + queue.insert(new_index, to_move); + } + pub fn remove_from_queue(&self, index: usize) { + self.edit().remove(index); + } + pub fn remove_from_queue_string(&self, game_id: String) { + let mut queue = self.edit(); + let current_index = get_index_from_id(&mut queue, game_id).unwrap(); + queue.remove(current_index); + } + pub fn pause_downloads(&self) -> Result<(), SendError> { + self.sender.send(DownloadManagerSignal::Stop) + } + pub fn resume_downloads(&self) -> Result<(), SendError> { + self.sender.send(DownloadManagerSignal::Go) + } + pub fn ensure_terminated(self) -> Result<(), ()> { + match self.terminator.join() { + Ok(o) => o, + Err(_) => Err(()), + } + } +} + +pub fn get_index_from_id(queue: &mut MutexGuard<'_, VecDeque>, id: String) -> Option { + queue.iter().position(|download_agent| { + download_agent == &id + }) +} diff --git a/desktop/src-tauri/src/downloads/mod.rs b/desktop/src-tauri/src/downloads/mod.rs index e166e3f9..cf0f5481 100644 --- a/desktop/src-tauri/src/downloads/mod.rs +++ b/desktop/src-tauri/src/downloads/mod.rs @@ -1,6 +1,7 @@ pub mod download_agent; pub mod download_commands; pub mod download_manager; +mod download_manager_interface; mod download_logic; mod download_thread_control_flag; mod manifest; diff --git a/desktop/src-tauri/src/downloads/progress_object.rs b/desktop/src-tauri/src/downloads/progress_object.rs index 6ed24000..884d126c 100644 --- a/desktop/src-tauri/src/downloads/progress_object.rs +++ b/desktop/src-tauri/src/downloads/progress_object.rs @@ -26,7 +26,7 @@ impl ProgressObject { .sum() } pub fn get_max(&self) -> usize { - self.max.lock().unwrap().clone() + *self.max.lock().unwrap() } pub fn set_max(&self, new_max: usize) { *self.max.lock().unwrap() = new_max diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs index deb7a4cc..b7e09459 100644 --- a/desktop/src-tauri/src/lib.rs +++ b/desktop/src-tauri/src/lib.rs @@ -9,7 +9,6 @@ mod settings; mod tests; use crate::db::DatabaseImpls; -use crate::downloads::download_agent::GameDownloadAgent; use auth::{auth_initiate, generate_authorization_header, recieve_handshake}; use db::{add_new_download_dir, DatabaseInterface, DATA_ROOT_DIR}; use downloads::download_commands::*; From f508391b6d2614d763f143786e35121e725ef5a0 Mon Sep 17 00:00:00 2001 From: quexeky Date: Wed, 13 Nov 2024 21:28:24 +1100 Subject: [PATCH 04/31] refactor(downloads): Ran cargo fmt Signed-off-by: quexeky --- .../src-tauri/src/downloads/download_agent.rs | 1 - .../src/downloads/download_commands.rs | 19 +++- .../src/downloads/download_manager.rs | 97 ++++++++++++------- .../downloads/download_manager_interface.rs | 52 +++++++--- desktop/src-tauri/src/downloads/mod.rs | 6 +- .../src/downloads/progress_object.rs | 3 +- desktop/src-tauri/src/lib.rs | 3 +- 7 files changed, 119 insertions(+), 62 deletions(-) diff --git a/desktop/src-tauri/src/downloads/download_agent.rs b/desktop/src-tauri/src/downloads/download_agent.rs index b582c7a0..7ef6f472 100644 --- a/desktop/src-tauri/src/downloads/download_agent.rs +++ b/desktop/src-tauri/src/downloads/download_agent.rs @@ -207,7 +207,6 @@ impl GameDownloadAgent { .build() .unwrap(); - pool.scope(move |scope| { let contexts = self.contexts.lock().unwrap(); diff --git a/desktop/src-tauri/src/downloads/download_commands.rs b/desktop/src-tauri/src/downloads/download_commands.rs index 74a1bd30..0a34e52d 100644 --- a/desktop/src-tauri/src/downloads/download_commands.rs +++ b/desktop/src-tauri/src/downloads/download_commands.rs @@ -1,7 +1,6 @@ use std::sync::Mutex; - -use crate::{AppState}; +use crate::AppState; #[tauri::command] pub fn download_game( @@ -27,7 +26,12 @@ pub fn download_game( download_agent_ref.clone().run(); }); */ - state.lock().unwrap().download_manager.queue_game(game_id, game_version, 0).unwrap(); + state + .lock() + .unwrap() + .download_manager + .queue_game(game_id, game_version, 0) + .unwrap(); Ok(()) } @@ -43,7 +47,12 @@ pub fn get_game_download_progress( Ok(progress.get_progress()) */ - let progress = state.lock().unwrap().download_manager.get_current_game_download_progress().unwrap_or(0.0); + let progress = state + .lock() + .unwrap() + .download_manager + .get_current_game_download_progress() + .unwrap_or(0.0); Ok(progress) } @@ -56,4 +65,4 @@ fn use_download_agent( let download_agent = lock.download_manager.get(&game_id).ok_or("Invalid game ID")?; Ok(download_agent.clone()) // Clones the Arc, not the underlying data structure } -*/ \ No newline at end of file +*/ diff --git a/desktop/src-tauri/src/downloads/download_manager.rs b/desktop/src-tauri/src/downloads/download_manager.rs index 7d2e44b1..0eb5d91f 100644 --- a/desktop/src-tauri/src/downloads/download_manager.rs +++ b/desktop/src-tauri/src/downloads/download_manager.rs @@ -1,10 +1,20 @@ use std::{ - collections::{HashMap, VecDeque}, sync::{mpsc::{channel, Receiver, SendError, Sender}, Arc, Mutex, MutexGuard}, thread::{spawn, JoinHandle}, + collections::{HashMap, VecDeque}, + sync::{ + mpsc::{channel, Receiver, Sender}, + Arc, Mutex, + }, + thread::spawn, }; use log::info; -use super::{download_agent::GameDownloadAgent, download_manager_interface::DownloadManagerInterface, download_thread_control_flag::{DownloadThreadControl, DownloadThreadControlFlag}, progress_object::ProgressObject}; +use super::{ + download_agent::GameDownloadAgent, + download_manager_interface::DownloadManagerInterface, + download_thread_control_flag::{DownloadThreadControl, DownloadThreadControlFlag}, + progress_object::ProgressObject, +}; pub struct DownloadManager { download_agent_registry: HashMap>, @@ -14,16 +24,16 @@ pub struct DownloadManager { progress: Arc>>, current_game_id: Option, // Should be the only game download agent in the map with the "Go" flag - active_control_flag: Option + active_control_flag: Option, } pub enum DownloadManagerSignal { Go, Stop, Completed(String), - Queue(String, String, usize) + Queue(String, String, usize), + Finish, } - impl DownloadManager { pub fn generate() -> DownloadManagerInterface { let queue = Arc::new(Mutex::new(VecDeque::new())); @@ -40,38 +50,40 @@ impl DownloadManager { progress: active_progress.clone(), }; - let terminator = spawn(|| {manager.manage_queue()}); + let terminator = spawn(|| manager.manage_queue()); - DownloadManagerInterface::new( - terminator, - queue, - active_progress, - sender, - ) + DownloadManagerInterface::new(terminator, queue, active_progress, sender) } fn manage_queue(mut self) -> Result<(), ()> { loop { let signal = match self.receiver.recv() { Ok(signal) => signal, - Err(e) => { - return Err(()) - }, + Err(e) => return Err(()), }; match signal { DownloadManagerSignal::Go => { self.manage_go_signal(); - }, + } DownloadManagerSignal::Stop => { self.manage_stop_signal(); - }, + } DownloadManagerSignal::Completed(game_id) => { self.manage_completed_signal(game_id); } DownloadManagerSignal::Queue(game_id, version, target_download_dir) => { self.manage_queue_signal(game_id, version, target_download_dir); - }, + } + DownloadManagerSignal::Finish => { + match self.active_control_flag { + Some(active_control_flag) => { + active_control_flag.set(DownloadThreadControlFlag::Stop) + } + None => {} + } + return Ok(()); + } }; } } @@ -82,7 +94,7 @@ impl DownloadManager { active_control_flag.set(DownloadThreadControlFlag::Stop); } } - + fn manage_completed_signal(&mut self, game_id: String) { info!("Got signal 'Completed'"); if self.current_game_id == Some(game_id.clone()) { @@ -94,47 +106,60 @@ impl DownloadManager { } self.sender.send(DownloadManagerSignal::Go).unwrap(); } - - fn manage_queue_signal(&mut self, game_id: String, version: String, target_download_dir: usize) { + + fn manage_queue_signal( + &mut self, + game_id: String, + version: String, + target_download_dir: usize, + ) { info!("Got signal Queue"); - let download_agent = Arc::new(GameDownloadAgent::new(game_id.clone(), version, target_download_dir)); - self.download_agent_registry.insert(game_id.clone(), download_agent); + let download_agent = Arc::new(GameDownloadAgent::new( + game_id.clone(), + version, + target_download_dir, + )); + self.download_agent_registry + .insert(game_id.clone(), download_agent); self.download_queue.lock().unwrap().push_back(game_id); } - + fn manage_go_signal(&mut self) { info!("Got signal 'Go'"); if self.active_control_flag.is_none() && !self.download_agent_registry.is_empty() { info!("Starting download agent"); let download_agent = { let lock = self.download_queue.lock().unwrap(); - self.download_agent_registry.get(&lock.front().unwrap().clone()).unwrap().clone() + self.download_agent_registry + .get(&lock.front().unwrap().clone()) + .unwrap() + .clone() }; self.current_game_id = Some(download_agent.id.clone()); - + let progress_object = download_agent.progress.clone(); *self.progress.lock().unwrap() = Some(progress_object); - + let active_control_flag = download_agent.control_flag.clone(); self.active_control_flag = Some(active_control_flag.clone()); - + let sender = self.sender.clone(); - + info!("Spawning download"); spawn(move || { download_agent.download().unwrap(); - sender.send(DownloadManagerSignal::Completed(download_agent.id.clone())).unwrap(); + sender + .send(DownloadManagerSignal::Completed(download_agent.id.clone())) + .unwrap(); }); info!("Finished spawning Download"); - + active_control_flag.set(DownloadThreadControlFlag::Go); - } - else if let Some(active_control_flag) = self.active_control_flag.clone() { + } else if let Some(active_control_flag) = self.active_control_flag.clone() { info!("Restarting current download"); active_control_flag.set(DownloadThreadControlFlag::Go); - } - else { + } else { info!("Nothing was set"); } } -} \ No newline at end of file +} diff --git a/desktop/src-tauri/src/downloads/download_manager_interface.rs b/desktop/src-tauri/src/downloads/download_manager_interface.rs index 40efdd65..6b552ccf 100644 --- a/desktop/src-tauri/src/downloads/download_manager_interface.rs +++ b/desktop/src-tauri/src/downloads/download_manager_interface.rs @@ -1,29 +1,50 @@ -use std::{collections::VecDeque, sync::{mpsc::{SendError, Sender}, Arc, Mutex, MutexGuard}, thread::JoinHandle}; +use std::{ + collections::VecDeque, + sync::{ + mpsc::{SendError, Sender}, + Arc, Mutex, MutexGuard, + }, + thread::JoinHandle, +}; use log::info; use super::{download_manager::DownloadManagerSignal, progress_object::ProgressObject}; pub struct DownloadManagerInterface { - terminator: JoinHandle>, + terminator: JoinHandle>, download_queue: Arc>>, progress: Arc>>, sender: Sender, } impl DownloadManagerInterface { - pub fn new( - terminator: JoinHandle>, - download_queue: Arc>>, - progress: Arc>>, - sender: Sender) -> Self { - Self { terminator, download_queue, progress, sender } + terminator: JoinHandle>, + download_queue: Arc>>, + progress: Arc>>, + sender: Sender, + ) -> Self { + Self { + terminator, + download_queue, + progress, + sender, + } } - - pub fn queue_game(&self, game_id: String, version: String, target_download_dir: usize) -> Result<(), SendError> { + + pub fn queue_game( + &self, + game_id: String, + version: String, + target_download_dir: usize, + ) -> Result<(), SendError> { info!("Adding game id {}", game_id); - self.sender.send(DownloadManagerSignal::Queue(game_id, version, target_download_dir))?; + self.sender.send(DownloadManagerSignal::Queue( + game_id, + version, + target_download_dir, + ))?; self.sender.send(DownloadManagerSignal::Go) } pub fn edit(&self) -> MutexGuard<'_, VecDeque> { @@ -59,6 +80,7 @@ impl DownloadManagerInterface { self.sender.send(DownloadManagerSignal::Go) } pub fn ensure_terminated(self) -> Result<(), ()> { + self.sender.send(DownloadManagerSignal::Finish).unwrap(); match self.terminator.join() { Ok(o) => o, Err(_) => Err(()), @@ -66,8 +88,8 @@ impl DownloadManagerInterface { } } -pub fn get_index_from_id(queue: &mut MutexGuard<'_, VecDeque>, id: String) -> Option { - queue.iter().position(|download_agent| { - download_agent == &id - }) +fn get_index_from_id(queue: &mut MutexGuard<'_, VecDeque>, id: String) -> Option { + queue + .iter() + .position(|download_agent| download_agent == &id) } diff --git a/desktop/src-tauri/src/downloads/mod.rs b/desktop/src-tauri/src/downloads/mod.rs index cf0f5481..08472eaf 100644 --- a/desktop/src-tauri/src/downloads/mod.rs +++ b/desktop/src-tauri/src/downloads/mod.rs @@ -1,8 +1,8 @@ pub mod download_agent; pub mod download_commands; -pub mod download_manager; -mod download_manager_interface; mod download_logic; +pub mod download_manager; +pub mod download_manager_interface; mod download_thread_control_flag; mod manifest; -mod progress_object; \ No newline at end of file +mod progress_object; diff --git a/desktop/src-tauri/src/downloads/progress_object.rs b/desktop/src-tauri/src/downloads/progress_object.rs index 884d126c..fb8a4dd2 100644 --- a/desktop/src-tauri/src/downloads/progress_object.rs +++ b/desktop/src-tauri/src/downloads/progress_object.rs @@ -32,7 +32,8 @@ impl ProgressObject { *self.max.lock().unwrap() = new_max } pub fn set_size(&self, length: usize) { - *self.progress_instances.lock().unwrap() = (0..length).map(|_| Arc::new(AtomicUsize::new(0))).collect(); + *self.progress_instances.lock().unwrap() = + (0..length).map(|_| Arc::new(AtomicUsize::new(0))).collect(); } pub fn get_progress(&self) -> f64 { diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs index b7e09459..6574c7c4 100644 --- a/desktop/src-tauri/src/lib.rs +++ b/desktop/src-tauri/src/lib.rs @@ -12,7 +12,8 @@ use crate::db::DatabaseImpls; use auth::{auth_initiate, generate_authorization_header, recieve_handshake}; use db::{add_new_download_dir, DatabaseInterface, DATA_ROOT_DIR}; use downloads::download_commands::*; -use downloads::download_manager::{DownloadManager, DownloadManagerInterface}; +use downloads::download_manager::DownloadManager; +use downloads::download_manager_interface::DownloadManagerInterface; use env_logger::Env; use http::{header::*, response::Builder as ResponseBuilder}; use library::{fetch_game, fetch_library, Game}; From 64286116898a7389673abc132d5c3873d4d6696b Mon Sep 17 00:00:00 2001 From: quexeky Date: Wed, 13 Nov 2024 21:55:28 +1100 Subject: [PATCH 05/31] style(downloads): Fixing some references to "id" vs "game_id" Signed-off-by: quexeky --- .../src/downloads/download_manager.rs | 20 ++++---- .../downloads/download_manager_interface.rs | 51 +++++++++++-------- 2 files changed, 39 insertions(+), 32 deletions(-) diff --git a/desktop/src-tauri/src/downloads/download_manager.rs b/desktop/src-tauri/src/downloads/download_manager.rs index 0eb5d91f..007839fb 100644 --- a/desktop/src-tauri/src/downloads/download_manager.rs +++ b/desktop/src-tauri/src/downloads/download_manager.rs @@ -19,7 +19,7 @@ use super::{ pub struct DownloadManager { download_agent_registry: HashMap>, download_queue: Arc>>, - receiver: Receiver, + command_receiver: Receiver, sender: Sender, progress: Arc>>, @@ -37,27 +37,27 @@ pub enum DownloadManagerSignal { impl DownloadManager { pub fn generate() -> DownloadManagerInterface { let queue = Arc::new(Mutex::new(VecDeque::new())); - let (sender, receiver) = channel(); + let (command_sender, command_receiver) = channel(); let active_progress = Arc::new(Mutex::new(None)); let manager = Self { download_agent_registry: HashMap::new(), download_queue: queue.clone(), - receiver, + command_receiver, current_game_id: None, active_control_flag: None, - sender: sender.clone(), + sender: command_sender.clone(), progress: active_progress.clone(), }; let terminator = spawn(|| manager.manage_queue()); - DownloadManagerInterface::new(terminator, queue, active_progress, sender) + DownloadManagerInterface::new(terminator, queue, active_progress, command_sender) } fn manage_queue(mut self) -> Result<(), ()> { loop { - let signal = match self.receiver.recv() { + let signal = match self.command_receiver.recv() { Ok(signal) => signal, Err(e) => return Err(()), }; @@ -109,19 +109,19 @@ impl DownloadManager { fn manage_queue_signal( &mut self, - game_id: String, + id: String, version: String, target_download_dir: usize, ) { info!("Got signal Queue"); let download_agent = Arc::new(GameDownloadAgent::new( - game_id.clone(), + id.clone(), version, target_download_dir, )); self.download_agent_registry - .insert(game_id.clone(), download_agent); - self.download_queue.lock().unwrap().push_back(game_id); + .insert(id.clone(), download_agent); + self.download_queue.lock().unwrap().push_back(id); } fn manage_go_signal(&mut self) { diff --git a/desktop/src-tauri/src/downloads/download_manager_interface.rs b/desktop/src-tauri/src/downloads/download_manager_interface.rs index 6b552ccf..71e1779f 100644 --- a/desktop/src-tauri/src/downloads/download_manager_interface.rs +++ b/desktop/src-tauri/src/downloads/download_manager_interface.rs @@ -1,21 +1,29 @@ use std::{ - collections::VecDeque, - sync::{ + any::Any, collections::VecDeque, sync::{ mpsc::{SendError, Sender}, Arc, Mutex, MutexGuard, - }, - thread::JoinHandle, + }, thread::JoinHandle }; use log::info; use super::{download_manager::DownloadManagerSignal, progress_object::ProgressObject}; +/// Accessible front-end for the DownloadManager +/// +/// The system works entirely through signals, both internally and externally, +/// all of which are accessible through the DownloadManagerSignal type, but +/// should not be used directly. Rather, signals are abstracted through this +/// interface. +/// +/// The actual download queue may be accessed through the .edit() function, +/// which provides raw access to the underlying queue. +/// THIS EDITING IS BLOCKING!!! pub struct DownloadManagerInterface { terminator: JoinHandle>, download_queue: Arc>>, progress: Arc>>, - sender: Sender, + command_sender: Sender, } impl DownloadManagerInterface { @@ -23,29 +31,29 @@ impl DownloadManagerInterface { terminator: JoinHandle>, download_queue: Arc>>, progress: Arc>>, - sender: Sender, + command_sender: Sender, ) -> Self { Self { terminator, download_queue, progress, - sender, + command_sender, } } pub fn queue_game( &self, - game_id: String, + id: String, version: String, target_download_dir: usize, ) -> Result<(), SendError> { - info!("Adding game id {}", game_id); - self.sender.send(DownloadManagerSignal::Queue( - game_id, + info!("Adding game id {}", id); + self.command_sender.send(DownloadManagerSignal::Queue( + id, version, target_download_dir, ))?; - self.sender.send(DownloadManagerSignal::Go) + self.command_sender.send(DownloadManagerSignal::Go) } pub fn edit(&self) -> MutexGuard<'_, VecDeque> { self.download_queue.lock().unwrap() @@ -68,26 +76,25 @@ impl DownloadManagerInterface { pub fn remove_from_queue(&self, index: usize) { self.edit().remove(index); } - pub fn remove_from_queue_string(&self, game_id: String) { + pub fn remove_from_queue_string(&self, id: String) { let mut queue = self.edit(); - let current_index = get_index_from_id(&mut queue, game_id).unwrap(); + let current_index = get_index_from_id(&mut queue, id).unwrap(); queue.remove(current_index); } pub fn pause_downloads(&self) -> Result<(), SendError> { - self.sender.send(DownloadManagerSignal::Stop) + self.command_sender.send(DownloadManagerSignal::Stop) } pub fn resume_downloads(&self) -> Result<(), SendError> { - self.sender.send(DownloadManagerSignal::Go) + self.command_sender.send(DownloadManagerSignal::Go) } - pub fn ensure_terminated(self) -> Result<(), ()> { - self.sender.send(DownloadManagerSignal::Finish).unwrap(); - match self.terminator.join() { - Ok(o) => o, - Err(_) => Err(()), - } + pub fn ensure_terminated(self) -> Result, Box> { + self.command_sender.send(DownloadManagerSignal::Finish).unwrap(); + self.terminator.join() } } +/// Takes in the locked value from .edit() and attempts to +/// get the index of whatever game_id is passed in fn get_index_from_id(queue: &mut MutexGuard<'_, VecDeque>, id: String) -> Option { queue .iter() From 18c7a9b2bcd94016701f1c8443c57117d6de0a3a Mon Sep 17 00:00:00 2001 From: quexeky Date: Wed, 13 Nov 2024 22:17:30 +1100 Subject: [PATCH 06/31] docs(download manager): Added description on how the DownloadManager works Signed-off-by: quexeky --- .../src/downloads/download_manager.rs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/desktop/src-tauri/src/downloads/download_manager.rs b/desktop/src-tauri/src/downloads/download_manager.rs index 007839fb..378d9896 100644 --- a/desktop/src-tauri/src/downloads/download_manager.rs +++ b/desktop/src-tauri/src/downloads/download_manager.rs @@ -16,6 +16,43 @@ use super::{ progress_object::ProgressObject, }; +/* + +Welcome to the download manager, the most overengineered, glorious piece of bullshit. + +The download manager takes a queue of game_ids and their associated +GameDownloadAgents, and then, one-by-one, executes them. It provides an interface +to interact with the currently downloading agent, and manage the queue. + +When the DownloadManager is initialised, it is designed to provide a reference +which can be used to provide some instructions (the DownloadManagerInterface), +but other than that, it runs without any sort of interruptions. + +It does this by opening up two data structures. Primarily is the command_receiver, +and mpsc (multi-channel-single-producer) which allows commands to be sent from +the Interface, and queued up for the Manager to process. + +These have been mapped in the DownloadManagerSignal docs. + +The other way to interact with the DownloadManager is via the donwload_queue, +which is just a collection of ids which may be rearranged to suit +whichever download queue order is required. + ++----------------------------------------------------------------------------+ +| DO NOT ATTEMPT TO ADD OR REMOVE FROM THE QUEUE WITHOUT USING SIGNALS!! | +| THIS WILL CAUSE A DESYNC BETWEEN THE DOWNLOAD AGENT REGISTRY AND THE QUEUE | +| WHICH HAS NOT BEEN ACCOUNTED FOR | ++----------------------------------------------------------------------------+ + +This download queue does not actually own any of the GameDownloadAgents. It is +simply a id-based reference system. The actual Agents are stored in the +download_agent_registry HashMap, as ordering is no issue here. This is why +appending or removing from the download_queue must be done via signals. + +Behold, my madness - quexeky + +*/ + pub struct DownloadManager { download_agent_registry: HashMap>, download_queue: Arc>>, @@ -27,10 +64,18 @@ pub struct DownloadManager { active_control_flag: Option, } pub enum DownloadManagerSignal { + /// Resumes (or starts) the DownloadManager Go, + /// Pauses the DownloadManager Stop, + /// Called when a GameDownloadAgent has finished. + /// Triggers the next download cycle to begin Completed(String), + /// Generates and appends a GameDownloadAgent + /// to the registry and queue Queue(String, String, usize), + /// Tells the Manager to stop the current + /// download and return Finish, } From b24e106420061ab1430310f33c9f9f329b4ac522 Mon Sep 17 00:00:00 2001 From: quexeky Date: Sat, 16 Nov 2024 17:03:37 +1100 Subject: [PATCH 07/31] feat(downloads): Added AgentInterfaceData to get information about all downloads in queue Signed-off-by: quexeky --- desktop/pages/store/index.vue | 2 +- .../src-tauri/src/downloads/download_agent.rs | 7 +- .../src/downloads/download_commands.rs | 10 +-- .../src-tauri/src/downloads/download_logic.rs | 2 +- .../src/downloads/download_manager.rs | 84 ++++++++++++++----- .../downloads/download_manager_interface.rs | 27 ++++-- desktop/src-tauri/src/lib.rs | 2 +- desktop/src-tauri/src/remote.rs | 8 +- 8 files changed, 98 insertions(+), 44 deletions(-) diff --git a/desktop/pages/store/index.vue b/desktop/pages/store/index.vue index ee5f7b22..18124edf 100644 --- a/desktop/pages/store/index.vue +++ b/desktop/pages/store/index.vue @@ -26,7 +26,7 @@ async function startGameDownload() { setInterval(() => { (async () => { const currentProgress = await invoke( - "get_game_download_progress", + "get_current_game_download_progress", { gameId: gameId.value, } diff --git a/desktop/src-tauri/src/downloads/download_agent.rs b/desktop/src-tauri/src/downloads/download_agent.rs index 7ef6f472..789d0fa1 100644 --- a/desktop/src-tauri/src/downloads/download_agent.rs +++ b/desktop/src-tauri/src/downloads/download_agent.rs @@ -28,7 +28,7 @@ pub struct GameDownloadAgent { pub progress: ProgressObject, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum GameDownloadError { CommunicationError(RemoteAccessError), ChecksumError, @@ -50,11 +50,11 @@ impl Display for GameDownloadError { impl GameDownloadAgent { pub fn new(id: String, version: String, target_download_dir: usize) -> Self { // Don't run by default - let status = DownloadThreadControl::new(DownloadThreadControlFlag::Stop); + let control_flag = DownloadThreadControl::new(DownloadThreadControlFlag::Stop); Self { id, version, - control_flag: status.clone(), + control_flag, manifest: Mutex::new(None), target_download_dir, contexts: Mutex::new(Vec::new()), @@ -207,6 +207,7 @@ impl GameDownloadAgent { .build() .unwrap(); + pool.scope(move |scope| { let contexts = self.contexts.lock().unwrap(); diff --git a/desktop/src-tauri/src/downloads/download_commands.rs b/desktop/src-tauri/src/downloads/download_commands.rs index 0a34e52d..df48f4ed 100644 --- a/desktop/src-tauri/src/downloads/download_commands.rs +++ b/desktop/src-tauri/src/downloads/download_commands.rs @@ -36,17 +36,9 @@ pub fn download_game( } #[tauri::command] -pub fn get_game_download_progress( +pub fn get_current_game_download_progress( state: tauri::State<'_, Mutex>, - game_id: String, ) -> Result { - /* - let download_agent = use_download_agent(state, game_id)?; - - let progress = &download_agent.progress; - - Ok(progress.get_progress()) - */ let progress = state .lock() .unwrap() diff --git a/desktop/src-tauri/src/downloads/download_logic.rs b/desktop/src-tauri/src/downloads/download_logic.rs index dc95f2fd..9478b1e7 100644 --- a/desktop/src-tauri/src/downloads/download_logic.rs +++ b/desktop/src-tauri/src/downloads/download_logic.rs @@ -148,7 +148,7 @@ pub fn download_game_chunk( .get(chunk_url) .header("Authorization", header) .send() - .map_err(|e| GameDownloadError::CommunicationError(RemoteAccessError::FetchError(e)))?; + .map_err(|e| GameDownloadError::CommunicationError(e.into()))?; let mut destination = DropWriter::new(ctx.path); diff --git a/desktop/src-tauri/src/downloads/download_manager.rs b/desktop/src-tauri/src/downloads/download_manager.rs index 378d9896..330b9832 100644 --- a/desktop/src-tauri/src/downloads/download_manager.rs +++ b/desktop/src-tauri/src/downloads/download_manager.rs @@ -10,8 +10,8 @@ use std::{ use log::info; use super::{ - download_agent::GameDownloadAgent, - download_manager_interface::DownloadManagerInterface, + download_agent::{GameDownloadAgent, GameDownloadError}, + download_manager_interface::{AgentInterfaceData, DownloadManagerInterface}, download_thread_control_flag::{DownloadThreadControl, DownloadThreadControlFlag}, progress_object::ProgressObject, }; @@ -55,12 +55,13 @@ Behold, my madness - quexeky pub struct DownloadManager { download_agent_registry: HashMap>, - download_queue: Arc>>, + download_queue: Arc>>>, command_receiver: Receiver, sender: Sender, progress: Arc>>, + status: Arc>, - current_game_id: Option, // Should be the only game download agent in the map with the "Go" flag + current_game_interface: Option>, // Should be the only game download agent in the map with the "Go" flag active_control_flag: Option, } pub enum DownloadManagerSignal { @@ -77,6 +78,21 @@ pub enum DownloadManagerSignal { /// Tells the Manager to stop the current /// download and return Finish, + /// Any error which occurs in the agent + Error(GameDownloadError) +} +pub enum DownloadManagerStatus { + Downloading, + Paused, + Empty, + Error(GameDownloadError), +} +#[derive(Clone)] +pub enum GameDownloadStatus { + Downloading, + Paused, + Uninitialised, + Error(GameDownloadError), } impl DownloadManager { @@ -84,13 +100,15 @@ impl DownloadManager { let queue = Arc::new(Mutex::new(VecDeque::new())); let (command_sender, command_receiver) = channel(); let active_progress = Arc::new(Mutex::new(None)); + let status = Arc::new(Mutex::new(DownloadManagerStatus::Empty)); let manager = Self { download_agent_registry: HashMap::new(), download_queue: queue.clone(), command_receiver, - current_game_id: None, + current_game_interface: None, active_control_flag: None, + status: status.clone(), sender: command_sender.clone(), progress: active_progress.clone(), }; @@ -110,6 +128,7 @@ impl DownloadManager { match signal { DownloadManagerSignal::Go => { self.manage_go_signal(); + } DownloadManagerSignal::Stop => { self.manage_stop_signal(); @@ -129,6 +148,9 @@ impl DownloadManager { } return Ok(()); } + DownloadManagerSignal::Error(game_download_error) => { + self.manage_error_signal(game_download_error); + }, }; } } @@ -142,12 +164,15 @@ impl DownloadManager { fn manage_completed_signal(&mut self, game_id: String) { info!("Got signal 'Completed'"); - if self.current_game_id == Some(game_id.clone()) { - info!("Popping consumed data"); - self.download_queue.lock().unwrap().pop_front(); - self.download_agent_registry.remove(&game_id); - self.active_control_flag = None; - *self.progress.lock().unwrap() = None; + if let Some(interface) = &self.current_game_interface { + // When if let chains are stabilised, combine these two statements + if interface.id == game_id { + info!("Popping consumed data"); + self.download_queue.lock().unwrap().pop_front(); + self.download_agent_registry.remove(&game_id); + self.active_control_flag = None; + *self.progress.lock().unwrap() = None; + } } self.sender.send(DownloadManagerSignal::Go).unwrap(); } @@ -164,9 +189,14 @@ impl DownloadManager { version, target_download_dir, )); + let agent_status = GameDownloadStatus::Uninitialised; + let interface_data = Arc::new(AgentInterfaceData { + id, + status: Mutex::new(agent_status), + }); self.download_agent_registry - .insert(id.clone(), download_agent); - self.download_queue.lock().unwrap().push_back(id); + .insert(interface_data.id.clone(), download_agent); + self.download_queue.lock().unwrap().push_back(interface_data); } fn manage_go_signal(&mut self) { @@ -176,11 +206,12 @@ impl DownloadManager { let download_agent = { let lock = self.download_queue.lock().unwrap(); self.download_agent_registry - .get(&lock.front().unwrap().clone()) + .get(&lock.front().unwrap().id) .unwrap() .clone() }; - self.current_game_id = Some(download_agent.id.clone()); + let download_agent_interface = Arc::new(AgentInterfaceData::from(download_agent.clone())); + self.current_game_interface = Some(download_agent_interface); let progress_object = download_agent.progress.clone(); *self.progress.lock().unwrap() = Some(progress_object); @@ -192,14 +223,20 @@ impl DownloadManager { info!("Spawning download"); spawn(move || { - download_agent.download().unwrap(); - sender - .send(DownloadManagerSignal::Completed(download_agent.id.clone())) - .unwrap(); + let signal = match download_agent.download() { + Ok(_) => { + DownloadManagerSignal::Completed(download_agent.id.clone()) + }, + Err(e) => { + DownloadManagerSignal::Error(e) + }, + }; + sender.send(signal).unwrap(); }); info!("Finished spawning Download"); active_control_flag.set(DownloadThreadControlFlag::Go); + self.set_status(DownloadManagerStatus::Downloading); } else if let Some(active_control_flag) = self.active_control_flag.clone() { info!("Restarting current download"); active_control_flag.set(DownloadThreadControlFlag::Go); @@ -207,4 +244,13 @@ impl DownloadManager { info!("Nothing was set"); } } + fn manage_error_signal(&self, error: GameDownloadError) { + let current_status = self.current_game_interface.clone().unwrap(); + let mut lock = current_status.status.lock().unwrap(); + *lock = GameDownloadStatus::Error(error.clone()); + self.set_status(DownloadManagerStatus::Error(error)); + } + fn set_status(&self, status: DownloadManagerStatus) { + *self.status.lock().unwrap() = status; + } } diff --git a/desktop/src-tauri/src/downloads/download_manager_interface.rs b/desktop/src-tauri/src/downloads/download_manager_interface.rs index 71e1779f..d68c92b9 100644 --- a/desktop/src-tauri/src/downloads/download_manager_interface.rs +++ b/desktop/src-tauri/src/downloads/download_manager_interface.rs @@ -7,7 +7,7 @@ use std::{ use log::info; -use super::{download_manager::DownloadManagerSignal, progress_object::ProgressObject}; +use super::{download_agent::GameDownloadAgent, download_manager::{DownloadManagerSignal, DownloadManagerStatus, GameDownloadStatus}, progress_object::ProgressObject}; /// Accessible front-end for the DownloadManager /// @@ -21,15 +21,27 @@ use super::{download_manager::DownloadManagerSignal, progress_object::ProgressOb /// THIS EDITING IS BLOCKING!!! pub struct DownloadManagerInterface { terminator: JoinHandle>, - download_queue: Arc>>, + download_queue: Arc>>>, progress: Arc>>, command_sender: Sender, } +pub struct AgentInterfaceData { + pub id: String, + pub status: Mutex, +} +impl From> for AgentInterfaceData { + fn from(value: Arc) -> Self { + Self { + id: value.id.clone(), + status: Mutex::from(GameDownloadStatus::Uninitialised) + } + } +} impl DownloadManagerInterface { pub fn new( terminator: JoinHandle>, - download_queue: Arc>>, + download_queue: Arc>>>, progress: Arc>>, command_sender: Sender, ) -> Self { @@ -55,9 +67,12 @@ impl DownloadManagerInterface { ))?; self.command_sender.send(DownloadManagerSignal::Go) } - pub fn edit(&self) -> MutexGuard<'_, VecDeque> { + pub fn edit(&self) -> MutexGuard<'_, VecDeque>> { self.download_queue.lock().unwrap() } + pub fn read_queue(&self) -> VecDeque> { + self.download_queue.lock().unwrap().clone() + } pub fn get_current_game_download_progress(&self) -> Option { let progress_object = (*self.progress.lock().unwrap()).clone()?; Some(progress_object.get_progress()) @@ -95,8 +110,8 @@ impl DownloadManagerInterface { /// Takes in the locked value from .edit() and attempts to /// get the index of whatever game_id is passed in -fn get_index_from_id(queue: &mut MutexGuard<'_, VecDeque>, id: String) -> Option { +fn get_index_from_id(queue: &mut MutexGuard<'_, VecDeque>>, id: String) -> Option { queue .iter() - .position(|download_agent| download_agent == &id) + .position(|download_agent| download_agent.id == id) } diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs index 6574c7c4..8982d9d6 100644 --- a/desktop/src-tauri/src/lib.rs +++ b/desktop/src-tauri/src/lib.rs @@ -124,7 +124,7 @@ pub fn run() { add_new_download_dir, // Downloads download_game, - get_game_download_progress, + get_current_game_download_progress, ]) .plugin(tauri_plugin_shell::init()) .setup(|app| { diff --git a/desktop/src-tauri/src/remote.rs b/desktop/src-tauri/src/remote.rs index e415c529..303a3dba 100644 --- a/desktop/src-tauri/src/remote.rs +++ b/desktop/src-tauri/src/remote.rs @@ -1,6 +1,6 @@ use std::{ fmt::{Display, Formatter}, - sync::Mutex, + sync::{Arc, Mutex}, }; use log::{info, warn}; @@ -9,9 +9,9 @@ use url::{ParseError, Url}; use crate::{AppState, AppStatus, DB}; -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum RemoteAccessError { - FetchError(reqwest::Error), + FetchError(Arc), ParsingError(ParseError), InvalidCodeError(u16), GenericErrror(String), @@ -32,7 +32,7 @@ impl Display for RemoteAccessError { impl From for RemoteAccessError { fn from(err: reqwest::Error) -> Self { - RemoteAccessError::FetchError(err) + RemoteAccessError::FetchError(Arc::new(err)) } } impl From for RemoteAccessError { From 2f245e56b6afcbe0f10aa9340ba4c3bae573abd2 Mon Sep 17 00:00:00 2001 From: quexeky Date: Sat, 16 Nov 2024 17:05:24 +1100 Subject: [PATCH 08/31] chore(downloads): Ran cargo clippy & cargo fmt Side note, I'm going to start using chore to declare these rather than refactor because I don't think that it actually qualifies Signed-off-by: quexeky --- .../src-tauri/src/downloads/download_agent.rs | 1 - .../src/downloads/download_manager.rs | 39 +++++++------------ .../downloads/download_manager_interface.rs | 34 ++++++++++------ 3 files changed, 38 insertions(+), 36 deletions(-) diff --git a/desktop/src-tauri/src/downloads/download_agent.rs b/desktop/src-tauri/src/downloads/download_agent.rs index 789d0fa1..14a61e0e 100644 --- a/desktop/src-tauri/src/downloads/download_agent.rs +++ b/desktop/src-tauri/src/downloads/download_agent.rs @@ -207,7 +207,6 @@ impl GameDownloadAgent { .build() .unwrap(); - pool.scope(move |scope| { let contexts = self.contexts.lock().unwrap(); diff --git a/desktop/src-tauri/src/downloads/download_manager.rs b/desktop/src-tauri/src/downloads/download_manager.rs index 330b9832..48779953 100644 --- a/desktop/src-tauri/src/downloads/download_manager.rs +++ b/desktop/src-tauri/src/downloads/download_manager.rs @@ -20,7 +20,7 @@ use super::{ Welcome to the download manager, the most overengineered, glorious piece of bullshit. -The download manager takes a queue of game_ids and their associated +The download manager takes a queue of game_ids and their associated GameDownloadAgents, and then, one-by-one, executes them. It provides an interface to interact with the currently downloading agent, and manage the queue. @@ -79,7 +79,7 @@ pub enum DownloadManagerSignal { /// download and return Finish, /// Any error which occurs in the agent - Error(GameDownloadError) + Error(GameDownloadError), } pub enum DownloadManagerStatus { Downloading, @@ -128,7 +128,6 @@ impl DownloadManager { match signal { DownloadManagerSignal::Go => { self.manage_go_signal(); - } DownloadManagerSignal::Stop => { self.manage_stop_signal(); @@ -140,17 +139,14 @@ impl DownloadManager { self.manage_queue_signal(game_id, version, target_download_dir); } DownloadManagerSignal::Finish => { - match self.active_control_flag { - Some(active_control_flag) => { - active_control_flag.set(DownloadThreadControlFlag::Stop) - } - None => {} + if let Some(active_control_flag) = self.active_control_flag { + active_control_flag.set(DownloadThreadControlFlag::Stop) } return Ok(()); } DownloadManagerSignal::Error(game_download_error) => { self.manage_error_signal(game_download_error); - }, + } }; } } @@ -171,18 +167,13 @@ impl DownloadManager { self.download_queue.lock().unwrap().pop_front(); self.download_agent_registry.remove(&game_id); self.active_control_flag = None; - *self.progress.lock().unwrap() = None; + *self.progress.lock().unwrap() = None; } } self.sender.send(DownloadManagerSignal::Go).unwrap(); } - fn manage_queue_signal( - &mut self, - id: String, - version: String, - target_download_dir: usize, - ) { + fn manage_queue_signal(&mut self, id: String, version: String, target_download_dir: usize) { info!("Got signal Queue"); let download_agent = Arc::new(GameDownloadAgent::new( id.clone(), @@ -196,7 +187,10 @@ impl DownloadManager { }); self.download_agent_registry .insert(interface_data.id.clone(), download_agent); - self.download_queue.lock().unwrap().push_back(interface_data); + self.download_queue + .lock() + .unwrap() + .push_back(interface_data); } fn manage_go_signal(&mut self) { @@ -210,7 +204,8 @@ impl DownloadManager { .unwrap() .clone() }; - let download_agent_interface = Arc::new(AgentInterfaceData::from(download_agent.clone())); + let download_agent_interface = + Arc::new(AgentInterfaceData::from(download_agent.clone())); self.current_game_interface = Some(download_agent_interface); let progress_object = download_agent.progress.clone(); @@ -224,12 +219,8 @@ impl DownloadManager { info!("Spawning download"); spawn(move || { let signal = match download_agent.download() { - Ok(_) => { - DownloadManagerSignal::Completed(download_agent.id.clone()) - }, - Err(e) => { - DownloadManagerSignal::Error(e) - }, + Ok(_) => DownloadManagerSignal::Completed(download_agent.id.clone()), + Err(e) => DownloadManagerSignal::Error(e), }; sender.send(signal).unwrap(); }); diff --git a/desktop/src-tauri/src/downloads/download_manager_interface.rs b/desktop/src-tauri/src/downloads/download_manager_interface.rs index d68c92b9..b5d56197 100644 --- a/desktop/src-tauri/src/downloads/download_manager_interface.rs +++ b/desktop/src-tauri/src/downloads/download_manager_interface.rs @@ -1,23 +1,30 @@ use std::{ - any::Any, collections::VecDeque, sync::{ + any::Any, + collections::VecDeque, + sync::{ mpsc::{SendError, Sender}, Arc, Mutex, MutexGuard, - }, thread::JoinHandle + }, + thread::JoinHandle, }; use log::info; -use super::{download_agent::GameDownloadAgent, download_manager::{DownloadManagerSignal, DownloadManagerStatus, GameDownloadStatus}, progress_object::ProgressObject}; +use super::{ + download_agent::GameDownloadAgent, + download_manager::{DownloadManagerSignal, GameDownloadStatus}, + progress_object::ProgressObject, +}; /// Accessible front-end for the DownloadManager -/// +/// /// The system works entirely through signals, both internally and externally, -/// all of which are accessible through the DownloadManagerSignal type, but +/// all of which are accessible through the DownloadManagerSignal type, but /// should not be used directly. Rather, signals are abstracted through this /// interface. -/// +/// /// The actual download queue may be accessed through the .edit() function, -/// which provides raw access to the underlying queue. +/// which provides raw access to the underlying queue. /// THIS EDITING IS BLOCKING!!! pub struct DownloadManagerInterface { terminator: JoinHandle>, @@ -33,7 +40,7 @@ impl From> for AgentInterfaceData { fn from(value: Arc) -> Self { Self { id: value.id.clone(), - status: Mutex::from(GameDownloadStatus::Uninitialised) + status: Mutex::from(GameDownloadStatus::Uninitialised), } } } @@ -102,15 +109,20 @@ impl DownloadManagerInterface { pub fn resume_downloads(&self) -> Result<(), SendError> { self.command_sender.send(DownloadManagerSignal::Go) } - pub fn ensure_terminated(self) -> Result, Box> { - self.command_sender.send(DownloadManagerSignal::Finish).unwrap(); + pub fn ensure_terminated(self) -> Result, Box> { + self.command_sender + .send(DownloadManagerSignal::Finish) + .unwrap(); self.terminator.join() } } /// Takes in the locked value from .edit() and attempts to /// get the index of whatever game_id is passed in -fn get_index_from_id(queue: &mut MutexGuard<'_, VecDeque>>, id: String) -> Option { +fn get_index_from_id( + queue: &mut MutexGuard<'_, VecDeque>>, + id: String, +) -> Option { queue .iter() .position(|download_agent| download_agent.id == id) From 639a1f52709829da8a7fd481cd2aa1fb3a57edd2 Mon Sep 17 00:00:00 2001 From: quexeky Date: Mon, 18 Nov 2024 13:21:20 +1100 Subject: [PATCH 09/31] style(downloads): Made all errors type-based Signed-off-by: quexeky --- desktop/src-tauri/src/auth.rs | 8 +--- .../src-tauri/src/downloads/download_agent.rs | 38 +++++++++---------- .../src/downloads/download_commands.rs | 38 ++++++------------- .../src-tauri/src/downloads/download_logic.rs | 10 ++--- .../src/downloads/download_manager.rs | 17 ++++----- .../downloads/download_manager_interface.rs | 4 +- desktop/src-tauri/src/lib.rs | 19 +++++++--- desktop/src-tauri/src/library.rs | 24 +++++------- desktop/src-tauri/src/remote.rs | 31 ++++++++++----- 9 files changed, 89 insertions(+), 100 deletions(-) diff --git a/desktop/src-tauri/src/auth.rs b/desktop/src-tauri/src/auth.rs index 7619e8ae..78da5b2a 100644 --- a/desktop/src-tauri/src/auth.rs +++ b/desktop/src-tauri/src/auth.rs @@ -93,9 +93,7 @@ fn recieve_handshake_logic(app: &AppHandle, path: String) -> Result<(), RemoteAc let path_chunks: Vec<&str> = path.split("/").collect(); if path_chunks.len() != 3 { app.emit("auth/failed", ()).unwrap(); - return Err(RemoteAccessError::GenericErrror( - "Invalid number of handshake chunks".to_string(), - )); + return Err(RemoteAccessError::InvalidResponse); } let base_url = { @@ -165,9 +163,7 @@ async fn auth_initiate_wrapper() -> Result<(), RemoteAccessError> { let response = client.post(endpoint.to_string()).json(&body).send().await?; if response.status() != 200 { - return Err("Failed to create redirect URL. Please try again later." - .to_string() - .into()); + return Err(RemoteAccessError::InvalidRedirect); } let redir_url = response.text().await?; diff --git a/desktop/src-tauri/src/downloads/download_agent.rs b/desktop/src-tauri/src/downloads/download_agent.rs index 14a61e0e..0b5e6104 100644 --- a/desktop/src-tauri/src/downloads/download_agent.rs +++ b/desktop/src-tauri/src/downloads/download_agent.rs @@ -28,21 +28,26 @@ pub struct GameDownloadAgent { pub progress: ProgressObject, } -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum GameDownloadError { - CommunicationError(RemoteAccessError), - ChecksumError, - SetupError(String), - LockError, + Communication(RemoteAccessError), + Checksum, + Setup(SetupError), + Lock, +} + +#[derive(Debug)] +pub enum SetupError { + Context } impl Display for GameDownloadError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - GameDownloadError::CommunicationError(error) => write!(f, "{}", error), - GameDownloadError::SetupError(error) => write!(f, "{}", error), - GameDownloadError::LockError => write!(f, "Failed to acquire lock. Something has gone very wrong internally. Please restart the application"), - GameDownloadError::ChecksumError => write!(f, "Checksum failed to validate for download"), + GameDownloadError::Communication(error) => write!(f, "{}", error), + GameDownloadError::Setup(error) => write!(f, "{:?}", error), + GameDownloadError::Lock => write!(f, "Failed to acquire lock. Something has gone very wrong internally. Please restart the application"), + GameDownloadError::Checksum => write!(f, "Checksum failed to validate for download"), } } } @@ -114,13 +119,8 @@ impl GameDownloadAgent { .unwrap(); if response.status() != 200 { - return Err(GameDownloadError::CommunicationError( - format!( - "Failed to download game manifest: {} {}", - response.status(), - response.text().unwrap() - ) - .into(), + return Err(GameDownloadError::Communication( + RemoteAccessError::ManifestDownloadFailed(response.status(), response.text().unwrap()) )); } @@ -143,7 +143,7 @@ impl GameDownloadAgent { return Ok(()); } - Err(GameDownloadError::LockError) + Err(GameDownloadError::Lock) } pub fn generate_contexts(&self) -> Result<(), GameDownloadError> { @@ -194,9 +194,7 @@ impl GameDownloadAgent { return Ok(()); } - Err(GameDownloadError::SetupError( - "Failed to generate download contexts".to_owned(), - )) + Err(GameDownloadError::Setup(SetupError::Context)) } pub fn run(&self) { diff --git a/desktop/src-tauri/src/downloads/download_commands.rs b/desktop/src-tauri/src/downloads/download_commands.rs index df48f4ed..d7d91555 100644 --- a/desktop/src-tauri/src/downloads/download_commands.rs +++ b/desktop/src-tauri/src/downloads/download_commands.rs @@ -1,52 +1,38 @@ use std::sync::Mutex; -use crate::AppState; +use serde::Serialize; + +use crate::{AppError, AppState}; #[tauri::command] pub fn download_game( game_id: String, game_version: String, state: tauri::State<'_, Mutex>, -) -> Result<(), String> { - /* - info!("beginning game download..."); - - let mut download_agent = GameDownloadAgent::new(game_id.clone(), game_version.clone(), 0); - // Setup download requires mutable - download_agent.setup_download().unwrap(); - - let mut lock: std::sync::MutexGuard<'_, AppState> = state.lock().unwrap(); - let download_agent_ref = Arc::new(download_agent); - lock.download_manager - .insert(game_id, download_agent_ref.clone()); - - // Run it in another thread - spawn(move || { - // Run doesn't require mutable - download_agent_ref.clone().run(); - }); - */ +) -> Result<(), AppError> { + state .lock() .unwrap() .download_manager .queue_game(game_id, game_version, 0) - .unwrap(); - Ok(()) + .map_err(|_| AppError::Signal) } #[tauri::command] pub fn get_current_game_download_progress( state: tauri::State<'_, Mutex>, -) -> Result { - let progress = state +) -> Result { + match state .lock() .unwrap() .download_manager .get_current_game_download_progress() - .unwrap_or(0.0); + { + Some(progress) => Ok(progress), + None => Err(AppError::DoesNotExist), + } - Ok(progress) } /* fn use_download_agent( diff --git a/desktop/src-tauri/src/downloads/download_logic.rs b/desktop/src-tauri/src/downloads/download_logic.rs index 9478b1e7..f094c96d 100644 --- a/desktop/src-tauri/src/downloads/download_logic.rs +++ b/desktop/src-tauri/src/downloads/download_logic.rs @@ -148,7 +148,7 @@ pub fn download_game_chunk( .get(chunk_url) .header("Authorization", header) .send() - .map_err(|e| GameDownloadError::CommunicationError(e.into()))?; + .map_err(|e| GameDownloadError::Communication(e.into()))?; let mut destination = DropWriter::new(ctx.path); @@ -160,11 +160,7 @@ pub fn download_game_chunk( let content_length = response.content_length(); if content_length.is_none() { - return Err(GameDownloadError::CommunicationError( - RemoteAccessError::GenericErrror( - "Invalid download endpoint, missing Content-Length header.".to_owned(), - ), - )); + return Err(GameDownloadError::Communication(RemoteAccessError::InvalidResponse)); } let mut pipeline = DropDownloadPipeline::new( @@ -184,7 +180,7 @@ pub fn download_game_chunk( let res = hex::encode(checksum.0); if res != ctx.checksum { - return Err(GameDownloadError::ChecksumError); + return Err(GameDownloadError::Checksum); } Ok(true) diff --git a/desktop/src-tauri/src/downloads/download_manager.rs b/desktop/src-tauri/src/downloads/download_manager.rs index 48779953..885ed444 100644 --- a/desktop/src-tauri/src/downloads/download_manager.rs +++ b/desktop/src-tauri/src/downloads/download_manager.rs @@ -11,7 +11,7 @@ use log::info; use super::{ download_agent::{GameDownloadAgent, GameDownloadError}, - download_manager_interface::{AgentInterfaceData, DownloadManagerInterface}, + download_manager_interface::{AgentInterfaceData, DownloadManager}, download_thread_control_flag::{DownloadThreadControl, DownloadThreadControlFlag}, progress_object::ProgressObject, }; @@ -53,7 +53,7 @@ Behold, my madness - quexeky */ -pub struct DownloadManager { +pub struct DownloadManagerBuilder { download_agent_registry: HashMap>, download_queue: Arc>>>, command_receiver: Receiver, @@ -85,9 +85,8 @@ pub enum DownloadManagerStatus { Downloading, Paused, Empty, - Error(GameDownloadError), + Error, } -#[derive(Clone)] pub enum GameDownloadStatus { Downloading, Paused, @@ -95,8 +94,8 @@ pub enum GameDownloadStatus { Error(GameDownloadError), } -impl DownloadManager { - pub fn generate() -> DownloadManagerInterface { +impl DownloadManagerBuilder { + pub fn build() -> DownloadManager { let queue = Arc::new(Mutex::new(VecDeque::new())); let (command_sender, command_receiver) = channel(); let active_progress = Arc::new(Mutex::new(None)); @@ -115,7 +114,7 @@ impl DownloadManager { let terminator = spawn(|| manager.manage_queue()); - DownloadManagerInterface::new(terminator, queue, active_progress, command_sender) + DownloadManager::new(terminator, queue, active_progress, command_sender) } fn manage_queue(mut self) -> Result<(), ()> { @@ -238,8 +237,8 @@ impl DownloadManager { fn manage_error_signal(&self, error: GameDownloadError) { let current_status = self.current_game_interface.clone().unwrap(); let mut lock = current_status.status.lock().unwrap(); - *lock = GameDownloadStatus::Error(error.clone()); - self.set_status(DownloadManagerStatus::Error(error)); + *lock = GameDownloadStatus::Error(error); + self.set_status(DownloadManagerStatus::Error); } fn set_status(&self, status: DownloadManagerStatus) { *self.status.lock().unwrap() = status; diff --git a/desktop/src-tauri/src/downloads/download_manager_interface.rs b/desktop/src-tauri/src/downloads/download_manager_interface.rs index b5d56197..00fd2040 100644 --- a/desktop/src-tauri/src/downloads/download_manager_interface.rs +++ b/desktop/src-tauri/src/downloads/download_manager_interface.rs @@ -26,7 +26,7 @@ use super::{ /// The actual download queue may be accessed through the .edit() function, /// which provides raw access to the underlying queue. /// THIS EDITING IS BLOCKING!!! -pub struct DownloadManagerInterface { +pub struct DownloadManager { terminator: JoinHandle>, download_queue: Arc>>>, progress: Arc>>, @@ -45,7 +45,7 @@ impl From> for AgentInterfaceData { } } -impl DownloadManagerInterface { +impl DownloadManager { pub fn new( terminator: JoinHandle>, download_queue: Arc>>>, diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs index 8982d9d6..5f9bd1fc 100644 --- a/desktop/src-tauri/src/lib.rs +++ b/desktop/src-tauri/src/lib.rs @@ -12,13 +12,13 @@ use crate::db::DatabaseImpls; use auth::{auth_initiate, generate_authorization_header, recieve_handshake}; use db::{add_new_download_dir, DatabaseInterface, DATA_ROOT_DIR}; use downloads::download_commands::*; -use downloads::download_manager::DownloadManager; -use downloads::download_manager_interface::DownloadManagerInterface; +use downloads::download_manager::DownloadManagerBuilder; +use downloads::download_manager_interface::DownloadManager; use env_logger::Env; use http::{header::*, response::Builder as ResponseBuilder}; use library::{fetch_game, fetch_library, Game}; use log::info; -use remote::{gen_drop_url, use_remote}; +use remote::{gen_drop_url, use_remote, RemoteAccessError}; use serde::{Deserialize, Serialize}; use std::sync::Arc; use std::{ @@ -36,6 +36,13 @@ pub enum AppStatus { SignedInNeedsReauth, ServerUnavailable, } +#[derive(Debug, Serialize)] +pub enum AppError { + DoesNotExist, + Signal, + RemoteAccess(String) +} + #[derive(Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct User { @@ -54,11 +61,11 @@ pub struct AppState { games: HashMap, #[serde(skip_serializing)] - download_manager: Arc, + download_manager: Arc, } #[tauri::command] -fn fetch_state(state: tauri::State<'_, Mutex>) -> Result { +fn fetch_state(state: tauri::State<'_, Mutex>) -> Result { let guard = state.lock().unwrap(); let cloned_state = guard.clone(); drop(guard); @@ -69,7 +76,7 @@ fn setup() -> AppState { env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); let games = HashMap::new(); - let download_manager = Arc::new(DownloadManager::generate()); + let download_manager = Arc::new(DownloadManagerBuilder::build()); let is_set_up = DB.database_is_set_up(); if !is_set_up { diff --git a/desktop/src-tauri/src/library.rs b/desktop/src-tauri/src/library.rs index 65f9ec10..11277494 100644 --- a/desktop/src-tauri/src/library.rs +++ b/desktop/src-tauri/src/library.rs @@ -7,6 +7,7 @@ use tauri::{AppHandle, Manager}; use crate::db::DatabaseGameStatus; use crate::db::DatabaseImpls; use crate::remote::RemoteAccessError; +use crate::AppError; use crate::{auth::generate_authorization_header, AppState, DB}; #[derive(serde::Serialize)] @@ -18,7 +19,7 @@ struct FetchGameStruct { #[derive(Serialize, Deserialize, Clone)] #[serde(rename_all = "camelCase")] pub struct Game { - id: String, + game_id: String, m_name: String, m_short_description: String, m_description: String, @@ -54,12 +55,12 @@ fn fetch_library_logic(app: AppHandle) -> Result { let mut db_handle = DB.borrow_data_mut().unwrap(); for game in games.iter() { - handle.games.insert(game.id.clone(), game.clone()); - if !db_handle.games.games_statuses.contains_key(&game.id) { + handle.games.insert(game.game_id.clone(), game.clone()); + if !db_handle.games.games_statuses.contains_key(&game.game_id) { db_handle .games .games_statuses - .insert(game.id.clone(), DatabaseGameStatus::Remote); + .insert(game.game_id.clone(), DatabaseGameStatus::Remote); } } @@ -69,14 +70,9 @@ fn fetch_library_logic(app: AppHandle) -> Result { } #[tauri::command] -pub fn fetch_library(app: AppHandle) -> Result { - let result = fetch_library_logic(app); - - if result.is_err() { - return Err(result.err().unwrap().to_string()); - } - - Ok(result.unwrap()) +pub fn fetch_library(app: AppHandle) -> Result { + fetch_library_logic(app) + .map_err(|e| AppError::RemoteAccess(e.to_string())) } fn fetch_game_logic(id: String, app: tauri::AppHandle) -> Result { @@ -92,7 +88,7 @@ fn fetch_game_logic(id: String, app: tauri::AppHandle) -> Result Result), ParsingError(ParseError), InvalidCodeError(u16), - GenericErrror(String), + InvalidEndpoint, + HandshakeFailed, + GameNotFound, + InvalidResponse, + InvalidRedirect, + ManifestDownloadFailed(StatusCode, String) } impl Display for RemoteAccessError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { RemoteAccessError::FetchError(error) => write!(f, "{}", error), - RemoteAccessError::GenericErrror(error) => write!(f, "{}", error), RemoteAccessError::ParsingError(parse_error) => { write!(f, "{}", parse_error) } RemoteAccessError::InvalidCodeError(error) => write!(f, "HTTP {}", error), + RemoteAccessError::ParsingError(parse_error) => todo!(), + RemoteAccessError::InvalidEndpoint => write!(f, "Invalid drop endpoint"), + RemoteAccessError::HandshakeFailed => write!(f, "Failed to complete handshake"), + RemoteAccessError::GameNotFound => write!(f, "Could not find game on server"), + RemoteAccessError::InvalidResponse => write!(f, "Server returned an invalid response"), + RemoteAccessError::InvalidRedirect => write!(f, "Server redirect was invalid"), + RemoteAccessError::ManifestDownloadFailed(status, response) => + write!(f, "Failed to download game manifest: {} {}", + status, + response + ), } } } @@ -35,11 +51,6 @@ impl From for RemoteAccessError { RemoteAccessError::FetchError(Arc::new(err)) } } -impl From for RemoteAccessError { - fn from(err: String) -> Self { - RemoteAccessError::GenericErrror(err) - } -} impl From for RemoteAccessError { fn from(err: ParseError) -> Self { RemoteAccessError::ParsingError(err) @@ -74,7 +85,7 @@ async fn use_remote_logic<'a>( if result.app_name != "Drop" { warn!("user entered drop endpoint that connected, but wasn't identified as Drop"); - return Err("Not a valid Drop endpoint".to_string().into()); + return Err(RemoteAccessError::InvalidEndpoint); } let mut app_state = state.lock().unwrap(); From 39743c8b64e652d0ab13d6ff4a14af94ce2cb36e Mon Sep 17 00:00:00 2001 From: DecDuck Date: Mon, 18 Nov 2024 20:13:10 +1100 Subject: [PATCH 10/31] fix(readme): update readme instructions --- desktop/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desktop/README.md b/desktop/README.md index f4b83060..b0df3337 100644 --- a/desktop/README.md +++ b/desktop/README.md @@ -6,7 +6,7 @@ Drop app is the companion app for [Drop](https://github.com/Drop-OSS/drop). It u Install dependencies with `yarn` -Run the app in development with `yarn tauri dev`. NVIDIA users on Linux, use the environment variable in `.env` +Run the app in development with `yarn tauri dev`. NVIDIA users on Linux, use shell script `./nvidia-prop-dev.sh` To manually specify the logging level, add the environment variable `RUST_LOG=[debug, info, warn, error]` to `yarn tauri dev`: From 5aff5c1a21a43707b902f0a09ae0c94d44d81c16 Mon Sep 17 00:00:00 2001 From: quexeky Date: Tue, 19 Nov 2024 13:02:28 +1100 Subject: [PATCH 11/31] chore(downloads): progress on more precise download control Signed-off-by: quexeky --- .../src-tauri/src/downloads/download_agent.rs | 46 ++++++++++++++++--- .../src/downloads/download_commands.rs | 13 +++++- .../src-tauri/src/downloads/download_logic.rs | 4 +- .../src/downloads/download_manager.rs | 22 +++++---- .../downloads/download_manager_interface.rs | 6 +++ 5 files changed, 72 insertions(+), 19 deletions(-) diff --git a/desktop/src-tauri/src/downloads/download_agent.rs b/desktop/src-tauri/src/downloads/download_agent.rs index 0b5e6104..81b19559 100644 --- a/desktop/src-tauri/src/downloads/download_agent.rs +++ b/desktop/src-tauri/src/downloads/download_agent.rs @@ -7,14 +7,17 @@ use log::info; use rayon::ThreadPoolBuilder; use std::fmt::{Display, Formatter}; use std::fs::{create_dir_all, File}; +use std::io; use std::path::Path; -use std::sync::Mutex; +use std::sync::mpsc::Sender; +use std::sync::{Arc, Mutex}; use urlencoding::encode; #[cfg(target_os = "linux")] use rustix::fs::{fallocate, FallocateFlags}; use super::download_logic::download_game_chunk; +use super::download_manager::DownloadManagerSignal; use super::download_thread_control_flag::{DownloadThreadControl, DownloadThreadControlFlag}; use super::progress_object::ProgressObject; @@ -26,6 +29,7 @@ pub struct GameDownloadAgent { contexts: Mutex>, pub manifest: Mutex>, pub progress: ProgressObject, + sender: Sender } #[derive(Debug)] @@ -34,6 +38,8 @@ pub enum GameDownloadError { Checksum, Setup(SetupError), Lock, + IoError(io::Error), + DownloadError } #[derive(Debug)] @@ -48,12 +54,14 @@ impl Display for GameDownloadError { GameDownloadError::Setup(error) => write!(f, "{:?}", error), GameDownloadError::Lock => write!(f, "Failed to acquire lock. Something has gone very wrong internally. Please restart the application"), GameDownloadError::Checksum => write!(f, "Checksum failed to validate for download"), + GameDownloadError::IoError(error) => write!(f, "{}", error), + GameDownloadError::DownloadError => write!(f, "Download failed. See Download Manager status for specific error"), } } } impl GameDownloadAgent { - pub fn new(id: String, version: String, target_download_dir: usize) -> Self { + pub fn new(id: String, version: String, target_download_dir: usize, sender: Sender) -> Self { // Don't run by default let control_flag = DownloadThreadControl::new(DownloadThreadControlFlag::Stop); Self { @@ -64,11 +72,11 @@ impl GameDownloadAgent { target_download_dir, contexts: Mutex::new(Vec::new()), progress: ProgressObject::new(0, 0), + sender } } // Blocking - // Requires mutable self pub fn setup_download(&self) -> Result<(), GameDownloadError> { self.ensure_manifest_exists()?; info!("Ensured manifest exists"); @@ -84,7 +92,7 @@ impl GameDownloadAgent { // Blocking pub fn download(&self) -> Result<(), GameDownloadError> { self.setup_download()?; - self.run(); + self.run().map_err(|_| GameDownloadError::DownloadError)?; Ok(()) } @@ -197,7 +205,7 @@ impl GameDownloadAgent { Err(GameDownloadError::Setup(SetupError::Context)) } - pub fn run(&self) { + pub fn run(&self) -> Result<(), ()> { const DOWNLOAD_MAX_THREADS: usize = 4; let pool = ThreadPoolBuilder::new() @@ -205,22 +213,48 @@ impl GameDownloadAgent { .build() .unwrap(); + let new_contexts = Arc::new(Mutex::new(Vec::new())); + let new_contexts_ref = new_contexts.clone(); + pool.scope(move |scope| { let contexts = self.contexts.lock().unwrap(); + for (index, context) in contexts.iter().enumerate() { let context = context.clone(); let control_flag = self.control_flag.clone(); // Clone arcs let progress = self.progress.get(index); // Clone arcs + let new_contexts_ref = new_contexts_ref.clone(); scope.spawn(move |_| { info!( "starting download for file {} {}", context.file_name, context.index ); - download_game_chunk(context, control_flag, progress).unwrap(); + match download_game_chunk(context.clone(), control_flag, progress) { + Ok(res) => { + match res { + true => {}, + false => new_contexts_ref.lock().unwrap().push(context), + } + }, + Err(e) => { + info!("GameDownloadError: {}", e); + self.sender.send(DownloadManagerSignal::Error(e)).unwrap(); + new_contexts_ref.lock().unwrap().push(context); + }, + } }); } }); + info!("Acquiring lock"); + if !new_contexts.lock().unwrap().is_empty() { + info!("New contexts not empty"); + *self.contexts.lock().unwrap() = Arc::into_inner(new_contexts).unwrap().into_inner().unwrap(); + info!("Contexts: {:?}", *self.contexts.lock().unwrap()); + return Err(()) + } + info!("Contexts: {:?}", *self.contexts.lock().unwrap()); + Ok(()) } } diff --git a/desktop/src-tauri/src/downloads/download_commands.rs b/desktop/src-tauri/src/downloads/download_commands.rs index d7d91555..f98a9fc8 100644 --- a/desktop/src-tauri/src/downloads/download_commands.rs +++ b/desktop/src-tauri/src/downloads/download_commands.rs @@ -1,7 +1,5 @@ use std::sync::Mutex; -use serde::Serialize; - use crate::{AppError, AppState}; #[tauri::command] @@ -32,7 +30,18 @@ pub fn get_current_game_download_progress( Some(progress) => Ok(progress), None => Err(AppError::DoesNotExist), } +} +#[tauri::command] +pub fn stop_game_download( + state: tauri::State<'_, Mutex>, + game_id: String +) { + state + .lock() + .unwrap() + .download_manager + .cancel_download(game_id); } /* fn use_download_agent( diff --git a/desktop/src-tauri/src/downloads/download_logic.rs b/desktop/src-tauri/src/downloads/download_logic.rs index f094c96d..6c99ec34 100644 --- a/desktop/src-tauri/src/downloads/download_logic.rs +++ b/desktop/src-tauri/src/downloads/download_logic.rs @@ -171,12 +171,12 @@ pub fn download_game_chunk( content_length.unwrap().try_into().unwrap(), ); - let completed = pipeline.copy().unwrap(); + let completed = pipeline.copy().map_err(|e| GameDownloadError::IoError(e))?; if !completed { return Ok(false); }; - let checksum = pipeline.finish().unwrap(); + let checksum = pipeline.finish().map_err(|e| GameDownloadError::IoError(e))?; let res = hex::encode(checksum.0); if res != ctx.checksum { diff --git a/desktop/src-tauri/src/downloads/download_manager.rs b/desktop/src-tauri/src/downloads/download_manager.rs index 885ed444..0b674e32 100644 --- a/desktop/src-tauri/src/downloads/download_manager.rs +++ b/desktop/src-tauri/src/downloads/download_manager.rs @@ -91,7 +91,7 @@ pub enum GameDownloadStatus { Downloading, Paused, Uninitialised, - Error(GameDownloadError), + Error, } impl DownloadManagerBuilder { @@ -143,9 +143,9 @@ impl DownloadManagerBuilder { } return Ok(()); } - DownloadManagerSignal::Error(game_download_error) => { - self.manage_error_signal(game_download_error); - } + DownloadManagerSignal::Error(e) => { + self.manage_error_signal(e); + }, }; } } @@ -178,6 +178,7 @@ impl DownloadManagerBuilder { id.clone(), version, target_download_dir, + self.sender.clone() )); let agent_status = GameDownloadStatus::Uninitialised; let interface_data = Arc::new(AgentInterfaceData { @@ -217,11 +218,14 @@ impl DownloadManagerBuilder { info!("Spawning download"); spawn(move || { - let signal = match download_agent.download() { - Ok(_) => DownloadManagerSignal::Completed(download_agent.id.clone()), - Err(e) => DownloadManagerSignal::Error(e), + match download_agent.download() { + Ok(_) => { + sender.send(DownloadManagerSignal::Completed(download_agent.id.clone())); + }, + Err(_) => { + todo!() // Account for if the setup_download function fails + }, }; - sender.send(signal).unwrap(); }); info!("Finished spawning Download"); @@ -237,7 +241,7 @@ impl DownloadManagerBuilder { fn manage_error_signal(&self, error: GameDownloadError) { let current_status = self.current_game_interface.clone().unwrap(); let mut lock = current_status.status.lock().unwrap(); - *lock = GameDownloadStatus::Error(error); + *lock = GameDownloadStatus::Error; self.set_status(DownloadManagerStatus::Error); } fn set_status(&self, status: DownloadManagerStatus) { diff --git a/desktop/src-tauri/src/downloads/download_manager_interface.rs b/desktop/src-tauri/src/downloads/download_manager_interface.rs index 00fd2040..d626267c 100644 --- a/desktop/src-tauri/src/downloads/download_manager_interface.rs +++ b/desktop/src-tauri/src/downloads/download_manager_interface.rs @@ -74,6 +74,12 @@ impl DownloadManager { ))?; self.command_sender.send(DownloadManagerSignal::Go) } + pub fn cancel_download( + &self, + id: String + ) { + todo!() + } pub fn edit(&self) -> MutexGuard<'_, VecDeque>> { self.download_queue.lock().unwrap() } From e074011ca84785846eaf2521bb23e02ff3180350 Mon Sep 17 00:00:00 2001 From: quexeky Date: Thu, 21 Nov 2024 16:46:05 +1100 Subject: [PATCH 12/31] feat(downloads): Download cancelling Signed-off-by: quexeky --- desktop/pages/store/index.vue | 10 ++++++- .../src-tauri/src/downloads/download_agent.rs | 9 +++--- .../src/downloads/download_commands.rs | 3 ++ .../src/downloads/download_manager.rs | 29 ++++++++++++++++--- .../downloads/download_manager_interface.rs | 4 +-- desktop/src-tauri/src/lib.rs | 1 + 6 files changed, 44 insertions(+), 12 deletions(-) diff --git a/desktop/pages/store/index.vue b/desktop/pages/store/index.vue index 18124edf..45170b5b 100644 --- a/desktop/pages/store/index.vue +++ b/desktop/pages/store/index.vue @@ -9,7 +9,12 @@ Download game ({{ Math.floor(progress * 1000) / 10 }}%) - + diff --git a/desktop/src-tauri/src/downloads/download_agent.rs b/desktop/src-tauri/src/downloads/download_agent.rs index 81b19559..cf5192b5 100644 --- a/desktop/src-tauri/src/downloads/download_agent.rs +++ b/desktop/src-tauri/src/downloads/download_agent.rs @@ -3,7 +3,7 @@ use crate::db::DatabaseImpls; use crate::downloads::manifest::{DropDownloadContext, DropManifest}; use crate::remote::RemoteAccessError; use crate::DB; -use log::info; +use log::{debug, error, info}; use rayon::ThreadPoolBuilder; use std::fmt::{Display, Formatter}; use std::fs::{create_dir_all, File}; @@ -239,7 +239,7 @@ impl GameDownloadAgent { } }, Err(e) => { - info!("GameDownloadError: {}", e); + error!("GameDownloadError: {}", e); self.sender.send(DownloadManagerSignal::Error(e)).unwrap(); new_contexts_ref.lock().unwrap().push(context); }, @@ -247,11 +247,10 @@ impl GameDownloadAgent { }); } }); - info!("Acquiring lock"); if !new_contexts.lock().unwrap().is_empty() { - info!("New contexts not empty"); + debug!("New contexts not empty"); *self.contexts.lock().unwrap() = Arc::into_inner(new_contexts).unwrap().into_inner().unwrap(); - info!("Contexts: {:?}", *self.contexts.lock().unwrap()); + debug!("Contexts: {:?}", *self.contexts.lock().unwrap()); return Err(()) } info!("Contexts: {:?}", *self.contexts.lock().unwrap()); diff --git a/desktop/src-tauri/src/downloads/download_commands.rs b/desktop/src-tauri/src/downloads/download_commands.rs index f98a9fc8..88838367 100644 --- a/desktop/src-tauri/src/downloads/download_commands.rs +++ b/desktop/src-tauri/src/downloads/download_commands.rs @@ -1,5 +1,7 @@ use std::sync::Mutex; +use log::info; + use crate::{AppError, AppState}; #[tauri::command] @@ -37,6 +39,7 @@ pub fn stop_game_download( state: tauri::State<'_, Mutex>, game_id: String ) { + info!("Cancelling game download {}", game_id); state .lock() .unwrap() diff --git a/desktop/src-tauri/src/downloads/download_manager.rs b/desktop/src-tauri/src/downloads/download_manager.rs index 0b674e32..a48034fd 100644 --- a/desktop/src-tauri/src/downloads/download_manager.rs +++ b/desktop/src-tauri/src/downloads/download_manager.rs @@ -7,7 +7,7 @@ use std::{ thread::spawn, }; -use log::info; +use log::{info, warn}; use super::{ download_agent::{GameDownloadAgent, GameDownloadError}, @@ -78,6 +78,7 @@ pub enum DownloadManagerSignal { /// Tells the Manager to stop the current /// download and return Finish, + Cancel(String), /// Any error which occurs in the agent Error(GameDownloadError), } @@ -146,6 +147,9 @@ impl DownloadManagerBuilder { DownloadManagerSignal::Error(e) => { self.manage_error_signal(e); }, + DownloadManagerSignal::Cancel(id) => { + self.manage_cancel_signal(id); + }, }; } } @@ -220,10 +224,11 @@ impl DownloadManagerBuilder { spawn(move || { match download_agent.download() { Ok(_) => { - sender.send(DownloadManagerSignal::Completed(download_agent.id.clone())); + sender.send(DownloadManagerSignal::Completed(download_agent.id.clone())).unwrap(); }, - Err(_) => { - todo!() // Account for if the setup_download function fails + Err(e) => { + warn!("Download failed"); + //todo!() // Account for if the setup_download function fails }, }; }); @@ -244,6 +249,22 @@ impl DownloadManagerBuilder { *lock = GameDownloadStatus::Error; self.set_status(DownloadManagerStatus::Error); } + fn manage_cancel_signal(&mut self, game_id: String) { + if let Some(current_flag) = &self.active_control_flag { + current_flag.set(DownloadThreadControlFlag::Stop); + self.active_control_flag = None; + *self.progress.lock().unwrap() = None; + } + self.download_agent_registry.remove(&game_id); + let mut lock = self.download_queue.lock().unwrap(); + let index = match lock.iter().position(|interface| interface.id == game_id) { + Some(index) => index, + None => return, + }; + lock.remove(index); + self.sender.send(DownloadManagerSignal::Go).unwrap(); + info!("{:?}", self.download_agent_registry.iter().map(|x| x.0.clone()).collect::()); + } fn set_status(&self, status: DownloadManagerStatus) { *self.status.lock().unwrap() = status; } diff --git a/desktop/src-tauri/src/downloads/download_manager_interface.rs b/desktop/src-tauri/src/downloads/download_manager_interface.rs index d626267c..790f6cd4 100644 --- a/desktop/src-tauri/src/downloads/download_manager_interface.rs +++ b/desktop/src-tauri/src/downloads/download_manager_interface.rs @@ -76,9 +76,9 @@ impl DownloadManager { } pub fn cancel_download( &self, - id: String + game_id: String ) { - todo!() + self.command_sender.send(DownloadManagerSignal::Cancel(game_id)).unwrap(); } pub fn edit(&self) -> MutexGuard<'_, VecDeque>> { self.download_queue.lock().unwrap() diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs index 5f9bd1fc..bd05f636 100644 --- a/desktop/src-tauri/src/lib.rs +++ b/desktop/src-tauri/src/lib.rs @@ -132,6 +132,7 @@ pub fn run() { // Downloads download_game, get_current_game_download_progress, + stop_game_download ]) .plugin(tauri_plugin_shell::init()) .setup(|app| { From b9960c1733a482fd5f048e42b85e7c016420e249 Mon Sep 17 00:00:00 2001 From: quexeky Date: Sat, 23 Nov 2024 18:18:03 +1100 Subject: [PATCH 13/31] style(downloads): Abstracted queue system TODO: Still need to cleanup the rest of the legacy code which used to use the queue system Signed-off-by: quexeky --- .../src-tauri/src/downloads/download_logic.rs | 3 +- .../src/downloads/download_manager.rs | 23 +++---- .../downloads/download_manager_interface.rs | 10 +-- desktop/src-tauri/src/downloads/mod.rs | 1 + desktop/src-tauri/src/downloads/queue.rs | 64 +++++++++++++++++++ 5 files changed, 82 insertions(+), 19 deletions(-) create mode 100644 desktop/src-tauri/src/downloads/queue.rs diff --git a/desktop/src-tauri/src/downloads/download_logic.rs b/desktop/src-tauri/src/downloads/download_logic.rs index 6c99ec34..942423a7 100644 --- a/desktop/src-tauri/src/downloads/download_logic.rs +++ b/desktop/src-tauri/src/downloads/download_logic.rs @@ -8,7 +8,7 @@ use md5::{Context, Digest}; use reqwest::blocking::Response; use std::io::Read; -use std::sync::atomic::AtomicUsize; +use std::sync::atomic::{AtomicUsize, Ordering}; use std::{ fs::{File, OpenOptions}, io::{self, BufWriter, ErrorKind, Seek, SeekFrom, Write}, @@ -125,6 +125,7 @@ pub fn download_game_chunk( // If we're paused if control_flag.get() == DownloadThreadControlFlag::Stop { info!("Control flag is Stop"); + progress.store(0, Ordering::Relaxed); return Ok(false); } diff --git a/desktop/src-tauri/src/downloads/download_manager.rs b/desktop/src-tauri/src/downloads/download_manager.rs index a48034fd..d71d801e 100644 --- a/desktop/src-tauri/src/downloads/download_manager.rs +++ b/desktop/src-tauri/src/downloads/download_manager.rs @@ -13,7 +13,7 @@ use super::{ download_agent::{GameDownloadAgent, GameDownloadError}, download_manager_interface::{AgentInterfaceData, DownloadManager}, download_thread_control_flag::{DownloadThreadControl, DownloadThreadControlFlag}, - progress_object::ProgressObject, + progress_object::ProgressObject, queue::Queue, }; /* @@ -55,7 +55,7 @@ Behold, my madness - quexeky pub struct DownloadManagerBuilder { download_agent_registry: HashMap>, - download_queue: Arc>>>, + download_queue: Queue, command_receiver: Receiver, sender: Sender, progress: Arc>>, @@ -97,7 +97,7 @@ pub enum GameDownloadStatus { impl DownloadManagerBuilder { pub fn build() -> DownloadManager { - let queue = Arc::new(Mutex::new(VecDeque::new())); + let queue = Queue::new(); let (command_sender, command_receiver) = channel(); let active_progress = Arc::new(Mutex::new(None)); let status = Arc::new(Mutex::new(DownloadManagerStatus::Empty)); @@ -167,7 +167,7 @@ impl DownloadManagerBuilder { // When if let chains are stabilised, combine these two statements if interface.id == game_id { info!("Popping consumed data"); - self.download_queue.lock().unwrap().pop_front(); + self.download_queue.pop_front(); self.download_agent_registry.remove(&game_id); self.active_control_flag = None; *self.progress.lock().unwrap() = None; @@ -185,16 +185,13 @@ impl DownloadManagerBuilder { self.sender.clone() )); let agent_status = GameDownloadStatus::Uninitialised; - let interface_data = Arc::new(AgentInterfaceData { + let interface_data = AgentInterfaceData { id, status: Mutex::new(agent_status), - }); + }; self.download_agent_registry .insert(interface_data.id.clone(), download_agent); - self.download_queue - .lock() - .unwrap() - .push_back(interface_data); + self.download_queue.append(interface_data); } fn manage_go_signal(&mut self) { @@ -202,9 +199,9 @@ impl DownloadManagerBuilder { if self.active_control_flag.is_none() && !self.download_agent_registry.is_empty() { info!("Starting download agent"); let download_agent = { - let lock = self.download_queue.lock().unwrap(); + let front = self.download_queue.read().front().unwrap().clone(); self.download_agent_registry - .get(&lock.front().unwrap().id) + .get(&front.id) .unwrap() .clone() }; @@ -256,7 +253,7 @@ impl DownloadManagerBuilder { *self.progress.lock().unwrap() = None; } self.download_agent_registry.remove(&game_id); - let mut lock = self.download_queue.lock().unwrap(); + let mut lock = self.download_queue.edit(); let index = match lock.iter().position(|interface| interface.id == game_id) { Some(index) => index, None => return, diff --git a/desktop/src-tauri/src/downloads/download_manager_interface.rs b/desktop/src-tauri/src/downloads/download_manager_interface.rs index 790f6cd4..0ecf2bd9 100644 --- a/desktop/src-tauri/src/downloads/download_manager_interface.rs +++ b/desktop/src-tauri/src/downloads/download_manager_interface.rs @@ -13,7 +13,7 @@ use log::info; use super::{ download_agent::GameDownloadAgent, download_manager::{DownloadManagerSignal, GameDownloadStatus}, - progress_object::ProgressObject, + progress_object::ProgressObject, queue::Queue, }; /// Accessible front-end for the DownloadManager @@ -28,7 +28,7 @@ use super::{ /// THIS EDITING IS BLOCKING!!! pub struct DownloadManager { terminator: JoinHandle>, - download_queue: Arc>>>, + download_queue: Queue, progress: Arc>>, command_sender: Sender, } @@ -48,7 +48,7 @@ impl From> for AgentInterfaceData { impl DownloadManager { pub fn new( terminator: JoinHandle>, - download_queue: Arc>>>, + download_queue: Queue, progress: Arc>>, command_sender: Sender, ) -> Self { @@ -81,10 +81,10 @@ impl DownloadManager { self.command_sender.send(DownloadManagerSignal::Cancel(game_id)).unwrap(); } pub fn edit(&self) -> MutexGuard<'_, VecDeque>> { - self.download_queue.lock().unwrap() + self.download_queue.edit() } pub fn read_queue(&self) -> VecDeque> { - self.download_queue.lock().unwrap().clone() + self.download_queue.read() } pub fn get_current_game_download_progress(&self) -> Option { let progress_object = (*self.progress.lock().unwrap()).clone()?; diff --git a/desktop/src-tauri/src/downloads/mod.rs b/desktop/src-tauri/src/downloads/mod.rs index 08472eaf..3556f81b 100644 --- a/desktop/src-tauri/src/downloads/mod.rs +++ b/desktop/src-tauri/src/downloads/mod.rs @@ -6,3 +6,4 @@ pub mod download_manager_interface; mod download_thread_control_flag; mod manifest; mod progress_object; +pub mod queue; \ No newline at end of file diff --git a/desktop/src-tauri/src/downloads/queue.rs b/desktop/src-tauri/src/downloads/queue.rs new file mode 100644 index 00000000..de1be177 --- /dev/null +++ b/desktop/src-tauri/src/downloads/queue.rs @@ -0,0 +1,64 @@ +use std::{collections::VecDeque, sync::{Arc, Mutex, MutexGuard}}; + +use super::download_manager_interface::AgentInterfaceData; + +#[derive(Clone)] +pub struct Queue { + inner: Arc>>> +} + +impl Queue { + pub fn new() -> Self { + Self { + inner: Arc::new(Mutex::new(VecDeque::new())) + } + } + pub fn read(&self) -> VecDeque> { + self.inner.lock().unwrap().clone() + } + pub fn edit(&self) -> MutexGuard<'_, VecDeque>> { + self.inner.lock().unwrap() + } + pub fn pop_front(&self) -> Option> { + self.edit().pop_front() + } + /// Either inserts `interface` at the specified index, or appends to + /// the back of the deque if index is greater than the length of the deque + pub fn insert(&self, interface: AgentInterfaceData, index: usize) { + if self.read().len() > index { + self.append(interface); + } + else { + self.edit().insert(index, Arc::new(interface)); + } + } + pub fn append(&self, interface: AgentInterfaceData) { + self.edit().push_back(Arc::new(interface)); + } + pub fn pop_front_if_equal(&self, game_id: String) -> Option> { + let mut queue = self.edit(); + let front = match queue.front() { + Some(front) => front, + None => return None, + }; + if front.id == game_id { + return queue.pop_front(); + } + return None + } + pub fn get_by_id(&self, game_id: String) -> Option { + self.read().iter().position(|data| data.id == game_id) + } + pub fn move_to_index_by_id(&self, game_id: String, new_index: usize) -> Result<(), ()> { + let index = match self.get_by_id(game_id) { + Some(index) => index, + None => return Err(()), + }; + let existing = match self.edit().remove(index) { + Some(existing) => existing, + None => return Err(()), + }; + self.edit().insert(new_index, existing); + Ok(()) + } +} \ No newline at end of file From b31d5c286a5a07b64932fdc3ec6c2b084b20e613 Mon Sep 17 00:00:00 2001 From: Louis van Liefland <116044207+quexeky@users.noreply.github.com> Date: Sat, 23 Nov 2024 23:32:56 +1100 Subject: [PATCH 14/31] chore(downloads): Progress on write speeds & added debug statements --- desktop/src-tauri/src/db.rs | 13 ++++++-- .../src-tauri/src/downloads/download_agent.rs | 30 +++++++++++-------- .../src/downloads/download_commands.rs | 8 +++++ desktop/src-tauri/src/downloads/manifest.rs | 1 + .../src/downloads/progress_object.rs | 9 ++++-- desktop/src-tauri/src/lib.rs | 6 +++- desktop/src-tauri/src/p2p/registration.rs | 17 +++++++++++ 7 files changed, 67 insertions(+), 17 deletions(-) create mode 100644 desktop/src-tauri/src/p2p/registration.rs diff --git a/desktop/src-tauri/src/db.rs b/desktop/src-tauri/src/db.rs index f5e5ea4d..4daf9844 100644 --- a/desktop/src-tauri/src/db.rs +++ b/desktop/src-tauri/src/db.rs @@ -6,7 +6,9 @@ use std::{ }; use directories::BaseDirs; +use log::debug; use rustbreak::{deser::Bincode, PathDatabase}; +use rustix::path::Arg; use serde::{Deserialize, Serialize}; use url::Url; @@ -61,7 +63,9 @@ impl DatabaseImpls for DatabaseInterface { let db_path = data_root_dir.join("drop.db"); let games_base_dir = data_root_dir.join("games"); + debug!("Creating data directory at {:?}", data_root_dir); create_dir_all(data_root_dir.clone()).unwrap(); + debug!("Creating games directory"); create_dir_all(games_base_dir.clone()).unwrap(); let default = Database { @@ -72,10 +76,15 @@ impl DatabaseImpls for DatabaseInterface { games_statuses: HashMap::new(), }, }; + #[allow(clippy::let_and_return)] - let db = match fs::exists(db_path.clone()).unwrap() { + let exists = fs::exists(db_path.clone()).unwrap(); + let db = match exists { true => PathDatabase::load_from_path(db_path).expect("Database loading failed"), - false => PathDatabase::create_at_path(db_path, default).unwrap(), + false => { + debug!("Creating database at path {}", db_path.as_str().unwrap()); + PathDatabase::create_at_path(db_path, default).expect("Database could not be created") + }, }; db diff --git a/desktop/src-tauri/src/downloads/download_agent.rs b/desktop/src-tauri/src/downloads/download_agent.rs index cf5192b5..0bb3b71c 100644 --- a/desktop/src-tauri/src/downloads/download_agent.rs +++ b/desktop/src-tauri/src/downloads/download_agent.rs @@ -92,6 +92,7 @@ impl GameDownloadAgent { // Blocking pub fn download(&self) -> Result<(), GameDownloadError> { self.setup_download()?; + self.set_progress_object_params(); self.run().map_err(|_| GameDownloadError::DownloadError)?; Ok(()) @@ -133,18 +134,6 @@ impl GameDownloadAgent { } let manifest_download = response.json::().unwrap(); - let length = manifest_download - .values() - .map(|chunk| { - return chunk.lengths.iter().sum::(); - }) - .sum::(); - let chunk_count = manifest_download - .values() - .map(|chunk| chunk.lengths.len()) - .sum(); - self.progress.set_max(length); - self.progress.set_size(chunk_count); if let Ok(mut manifest) = self.manifest.lock() { *manifest = Some(manifest_download); @@ -154,6 +143,22 @@ impl GameDownloadAgent { Err(GameDownloadError::Lock) } + fn set_progress_object_params(&self) { + let lock = self.contexts.lock().unwrap(); + let length = lock.len(); + + let chunk_count = lock.iter() + .map(|chunk| chunk.length) + .sum(); + + debug!("Setting ProgressObject max to {}", chunk_count); + self.progress.set_max(chunk_count); + debug!("Setting ProgressObject size to {}", length); + self.progress.set_size(length); + debug!("Setting ProgressObject time to now"); + self.progress.set_time_now(); + } + pub fn generate_contexts(&self) -> Result<(), GameDownloadError> { let db_lock = DB.borrow_data().unwrap(); let data_base_dir = db_lock.games.install_dirs[self.target_download_dir].clone(); @@ -187,6 +192,7 @@ impl GameDownloadAgent { game_id: game_id.to_string(), path: path.clone(), checksum: chunk.checksums[i].clone(), + length: *length }); running_offset += *length as u64; } diff --git a/desktop/src-tauri/src/downloads/download_commands.rs b/desktop/src-tauri/src/downloads/download_commands.rs index 88838367..5b429dfb 100644 --- a/desktop/src-tauri/src/downloads/download_commands.rs +++ b/desktop/src-tauri/src/downloads/download_commands.rs @@ -46,6 +46,14 @@ pub fn stop_game_download( .download_manager .cancel_download(game_id); } +#[tauri::command] +pub fn get_current_write_speed( + state: tauri::State<'_, Mutex>, +) { + +} + + /* fn use_download_agent( state: tauri::State<'_, Mutex>, diff --git a/desktop/src-tauri/src/downloads/manifest.rs b/desktop/src-tauri/src/downloads/manifest.rs index 2bb15520..2c28ea5a 100644 --- a/desktop/src-tauri/src/downloads/manifest.rs +++ b/desktop/src-tauri/src/downloads/manifest.rs @@ -20,4 +20,5 @@ pub struct DropDownloadContext { pub game_id: String, pub path: PathBuf, pub checksum: String, + pub length: usize } diff --git a/desktop/src-tauri/src/downloads/progress_object.rs b/desktop/src-tauri/src/downloads/progress_object.rs index fb8a4dd2..0848caf1 100644 --- a/desktop/src-tauri/src/downloads/progress_object.rs +++ b/desktop/src-tauri/src/downloads/progress_object.rs @@ -1,12 +1,13 @@ -use std::sync::{ +use std::{sync::{ atomic::{AtomicUsize, Ordering}, Arc, Mutex, -}; +}, time::Instant}; #[derive(Clone)] pub struct ProgressObject { max: Arc>, progress_instances: Arc>>>, + start: Arc> } impl ProgressObject { @@ -15,8 +16,12 @@ impl ProgressObject { Self { max: Arc::new(Mutex::new(max)), progress_instances: Arc::new(arr), + start: Arc::new(Mutex::new(Instant::now())), } } + pub fn set_time_now(&self) { + *self.start.lock().unwrap() = Instant::now(); + } pub fn sum(&self) -> usize { self.progress_instances .lock() diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs index bd05f636..beb1aeb7 100644 --- a/desktop/src-tauri/src/lib.rs +++ b/desktop/src-tauri/src/lib.rs @@ -17,7 +17,7 @@ use downloads::download_manager_interface::DownloadManager; use env_logger::Env; use http::{header::*, response::Builder as ResponseBuilder}; use library::{fetch_game, fetch_library, Game}; -use log::info; +use log::{debug, info}; use remote::{gen_drop_url, use_remote, RemoteAccessError}; use serde::{Deserialize, Serialize}; use std::sync::Arc; @@ -73,11 +73,13 @@ fn fetch_state(state: tauri::State<'_, Mutex>) -> Result AppState { + debug!("Starting env"); env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); let games = HashMap::new(); let download_manager = Arc::new(DownloadManagerBuilder::build()); + debug!("Checking if database is set up"); let is_set_up = DB.database_is_set_up(); if !is_set_up { return AppState { @@ -88,6 +90,8 @@ fn setup() -> AppState { }; } + debug!("Database is set up"); + let (app_status, user) = auth::setup().unwrap(); AppState { status: app_status, diff --git a/desktop/src-tauri/src/p2p/registration.rs b/desktop/src-tauri/src/p2p/registration.rs new file mode 100644 index 00000000..0926515e --- /dev/null +++ b/desktop/src-tauri/src/p2p/registration.rs @@ -0,0 +1,17 @@ +use crate::{auth::generate_authorization_header, db::DatabaseImpls, remote::RemoteAccessError, DB}; + + +pub async fn register() -> Result { + let base_url = DB.fetch_base_url(); + let registration_url = base_url.join("/api/v1/client/capability").unwrap(); + let header = generate_authorization_header(); + + + let client = reqwest::blocking::Client::new(); + client + .post(registration_url) + .header("Authorization", header) + .send()?; + + return Ok(String::new()) +} \ No newline at end of file From 104e90345423dd5ada7b8ca5eaf6ca41fb69b601 Mon Sep 17 00:00:00 2001 From: DecDuck Date: Sun, 24 Nov 2024 21:04:56 +1100 Subject: [PATCH 15/31] feat(settings): ability to add more download dirs --- desktop/components/HeaderUserWidget.vue | 2 +- desktop/composables/current-page-engine.ts | 2 +- desktop/package.json | 2 +- desktop/pages/library/[id]/index.vue | 2 +- desktop/pages/settings.vue | 8 +- desktop/pages/settings/downloads.vue | 218 +++++++++++++++++- desktop/src-tauri/Cargo.lock | 28 +-- desktop/src-tauri/src/db.rs | 40 ++-- .../src/downloads/download_commands.rs | 29 +-- desktop/src-tauri/src/lib.rs | 12 +- desktop/src-tauri/src/library.rs | 17 +- desktop/src-tauri/src/remote.rs | 3 +- desktop/yarn.lock | 8 +- 13 files changed, 291 insertions(+), 80 deletions(-) diff --git a/desktop/components/HeaderUserWidget.vue b/desktop/components/HeaderUserWidget.vue index 410ba72d..27f2867d 100644 --- a/desktop/components/HeaderUserWidget.vue +++ b/desktop/components/HeaderUserWidget.vue @@ -21,7 +21,7 @@ leave-to-class="transform opacity-0 scale-95" > diff --git a/desktop/package.json b/desktop/package.json index 163e8f33..49c2009f 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -17,7 +17,7 @@ "@prisma/client": "5.20.0", "@tauri-apps/api": ">=2.0.0", "@tauri-apps/plugin-deep-link": "~2", - "@tauri-apps/plugin-dialog": "~2", + "@tauri-apps/plugin-dialog": "^2.0.1", "@tauri-apps/plugin-shell": ">=2.0.0", "nuxt": "^3.13.0", "scss": "^0.2.4", diff --git a/desktop/pages/library/[id]/index.vue b/desktop/pages/library/[id]/index.vue index 2341cab2..94275bad 100644 --- a/desktop/pages/library/[id]/index.vue +++ b/desktop/pages/library/[id]/index.vue @@ -3,7 +3,7 @@ class="mx-auto w-full relative flex flex-col justify-center pt-64 z-10 overflow-hidden" > -
+

@@ -22,8 +22,8 @@ :is="item.icon" :class="[ itemIdx === currentPageIndex - ? 'text-blue-600' - : 'text-zinc-400 group-hover:text-blue-600', + ? 'text-zinc-100' + : 'text-zinc-400 group-hover:text-zinc-200', 'transition h-6 w-6 shrink-0', ]" aria-hidden="true" diff --git a/desktop/pages/settings/downloads.vue b/desktop/pages/settings/downloads.vue index 27e0f695..0878be79 100644 --- a/desktop/pages/settings/downloads.vue +++ b/desktop/pages/settings/downloads.vue @@ -1,3 +1,217 @@ \ No newline at end of file +
+
+
+
+

+ Install directories +

+

+ This is where Drop will download game files to, and store them + indefinitely while you play. Drop and games may store other + information elsewhere, like saves or mods. +

+
+
+ +
+
+
+
    +
  • +
    + +
    +

    + {{ dir }} +

    +
    +
    +
    + +
    +
  • +
+
+ + + +
+ + +
+
+ + +
+
+
+ +
+ +
+

+ Select an empty directory to add. +

+
+
+
+
+ + Upload + + +
+
+
+
+
+
+

+ {{ error }} +

+
+
+
+
+
+
+
+
+
+ + + diff --git a/desktop/src-tauri/Cargo.lock b/desktop/src-tauri/Cargo.lock index 0b030427..88505d99 100644 --- a/desktop/src-tauri/Cargo.lock +++ b/desktop/src-tauri/Cargo.lock @@ -58,9 +58,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -73,36 +73,36 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.4" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -612,9 +612,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "combine" diff --git a/desktop/src-tauri/src/db.rs b/desktop/src-tauri/src/db.rs index 4daf9844..c6d32e7e 100644 --- a/desktop/src-tauri/src/db.rs +++ b/desktop/src-tauri/src/db.rs @@ -6,7 +6,7 @@ use std::{ }; use directories::BaseDirs; -use log::debug; +use log::{debug, info}; use rustbreak::{deser::Bincode, PathDatabase}; use rustix::path::Arg; use serde::{Deserialize, Serialize}; @@ -68,23 +68,23 @@ impl DatabaseImpls for DatabaseInterface { debug!("Creating games directory"); create_dir_all(games_base_dir.clone()).unwrap(); - let default = Database { - auth: None, - base_url: "".to_string(), - games: DatabaseGames { - install_dirs: vec![games_base_dir.to_str().unwrap().to_string()], - games_statuses: HashMap::new(), - }, - }; - #[allow(clippy::let_and_return)] let exists = fs::exists(db_path.clone()).unwrap(); let db = match exists { true => PathDatabase::load_from_path(db_path).expect("Database loading failed"), false => { + let default = Database { + auth: None, + base_url: "".to_string(), + games: DatabaseGames { + install_dirs: vec![games_base_dir.to_str().unwrap().to_string()], + games_statuses: HashMap::new(), + }, + }; debug!("Creating database at path {}", db_path.as_str().unwrap()); - PathDatabase::create_at_path(db_path, default).expect("Database could not be created") - }, + PathDatabase::create_at_path(db_path, default) + .expect("Database could not be created") + } }; db @@ -114,8 +114,8 @@ pub fn add_new_download_dir(new_dir: String) -> Result<(), String> { let dir_contents = new_dir_path .read_dir() .map_err(|e| format!("Unable to check directory contents: {}", e))?; - if dir_contents.count() == 0 { - return Err("Path is not empty".to_string()); + if dir_contents.count() != 0 { + return Err("Directory is not empty".to_string()); } } else { create_dir_all(new_dir_path) @@ -126,6 +126,18 @@ pub fn add_new_download_dir(new_dir: String) -> Result<(), String> { let mut lock = DB.borrow_data_mut().unwrap(); lock.games.install_dirs.push(new_dir); drop(lock); + DB.save().unwrap(); Ok(()) } + +// Will, in future, return disk/remaining size +// Just returns the directories that have been set up +#[tauri::command] +pub fn fetch_download_dir_stats() -> Result, String> { + let lock = DB.borrow_data().unwrap(); + let directories = lock.games.install_dirs.clone(); + drop(lock); + + Ok(directories) +} diff --git a/desktop/src-tauri/src/downloads/download_commands.rs b/desktop/src-tauri/src/downloads/download_commands.rs index 5b429dfb..56441067 100644 --- a/desktop/src-tauri/src/downloads/download_commands.rs +++ b/desktop/src-tauri/src/downloads/download_commands.rs @@ -2,43 +2,39 @@ use std::sync::Mutex; use log::info; -use crate::{AppError, AppState}; +use crate::AppState; #[tauri::command] pub fn download_game( game_id: String, game_version: String, state: tauri::State<'_, Mutex>, -) -> Result<(), AppError> { - +) -> Result<(), String> { state .lock() .unwrap() .download_manager .queue_game(game_id, game_version, 0) - .map_err(|_| AppError::Signal) + .map_err(|_| "An error occurred while communicating with the download manager.".to_string()) } #[tauri::command] pub fn get_current_game_download_progress( state: tauri::State<'_, Mutex>, -) -> Result { +) -> Result { match state .lock() .unwrap() .download_manager .get_current_game_download_progress() - { - Some(progress) => Ok(progress), - None => Err(AppError::DoesNotExist), - } + { + Some(progress) => Ok(progress), + None => Err("Game does not exist".to_string()), + } } #[tauri::command] -pub fn stop_game_download( - state: tauri::State<'_, Mutex>, - game_id: String -) { +pub fn stop_game_download(state: tauri::State<'_, Mutex>, game_id: String) { info!("Cancelling game download {}", game_id); state .lock() @@ -47,12 +43,7 @@ pub fn stop_game_download( .cancel_download(game_id); } #[tauri::command] -pub fn get_current_write_speed( - state: tauri::State<'_, Mutex>, -) { - -} - +pub fn get_current_write_speed(state: tauri::State<'_, Mutex>) {} /* fn use_download_agent( diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs index beb1aeb7..707b787a 100644 --- a/desktop/src-tauri/src/lib.rs +++ b/desktop/src-tauri/src/lib.rs @@ -10,7 +10,7 @@ mod tests; use crate::db::DatabaseImpls; use auth::{auth_initiate, generate_authorization_header, recieve_handshake}; -use db::{add_new_download_dir, DatabaseInterface, DATA_ROOT_DIR}; +use db::{add_new_download_dir, fetch_download_dir_stats, DatabaseInterface, DATA_ROOT_DIR}; use downloads::download_commands::*; use downloads::download_manager::DownloadManagerBuilder; use downloads::download_manager_interface::DownloadManager; @@ -36,12 +36,6 @@ pub enum AppStatus { SignedInNeedsReauth, ServerUnavailable, } -#[derive(Debug, Serialize)] -pub enum AppError { - DoesNotExist, - Signal, - RemoteAccess(String) -} #[derive(Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -65,7 +59,7 @@ pub struct AppState { } #[tauri::command] -fn fetch_state(state: tauri::State<'_, Mutex>) -> Result { +fn fetch_state(state: tauri::State<'_, Mutex>) -> Result { let guard = state.lock().unwrap(); let cloned_state = guard.clone(); drop(guard); @@ -133,12 +127,14 @@ pub fn run() { fetch_library, fetch_game, add_new_download_dir, + fetch_download_dir_stats, // Downloads download_game, get_current_game_download_progress, stop_game_download ]) .plugin(tauri_plugin_shell::init()) + .plugin(tauri_plugin_dialog::init()) .setup(|app| { #[cfg(any(target_os = "linux", all(debug_assertions, windows)))] { diff --git a/desktop/src-tauri/src/library.rs b/desktop/src-tauri/src/library.rs index 11277494..50da6af6 100644 --- a/desktop/src-tauri/src/library.rs +++ b/desktop/src-tauri/src/library.rs @@ -1,5 +1,6 @@ use std::sync::Mutex; +use log::info; use serde::{Deserialize, Serialize}; use serde_json::json; use tauri::{AppHandle, Manager}; @@ -7,7 +8,6 @@ use tauri::{AppHandle, Manager}; use crate::db::DatabaseGameStatus; use crate::db::DatabaseImpls; use crate::remote::RemoteAccessError; -use crate::AppError; use crate::{auth::generate_authorization_header, AppState, DB}; #[derive(serde::Serialize)] @@ -19,7 +19,7 @@ struct FetchGameStruct { #[derive(Serialize, Deserialize, Clone)] #[serde(rename_all = "camelCase")] pub struct Game { - game_id: String, + id: String, m_name: String, m_short_description: String, m_description: String, @@ -55,12 +55,12 @@ fn fetch_library_logic(app: AppHandle) -> Result { let mut db_handle = DB.borrow_data_mut().unwrap(); for game in games.iter() { - handle.games.insert(game.game_id.clone(), game.clone()); - if !db_handle.games.games_statuses.contains_key(&game.game_id) { + handle.games.insert(game.id.clone(), game.clone()); + if !db_handle.games.games_statuses.contains_key(&game.id) { db_handle .games .games_statuses - .insert(game.game_id.clone(), DatabaseGameStatus::Remote); + .insert(game.id.clone(), DatabaseGameStatus::Remote); } } @@ -70,9 +70,8 @@ fn fetch_library_logic(app: AppHandle) -> Result { } #[tauri::command] -pub fn fetch_library(app: AppHandle) -> Result { - fetch_library_logic(app) - .map_err(|e| AppError::RemoteAccess(e.to_string())) +pub fn fetch_library(app: AppHandle) -> Result { + fetch_library_logic(app).map_err(|e| e.to_string()) } fn fetch_game_logic(id: String, app: tauri::AppHandle) -> Result { @@ -88,7 +87,7 @@ fn fetch_game_logic(id: String, app: tauri::AppHandle) -> Result write!(f, "HTTP {}", error), - RemoteAccessError::ParsingError(parse_error) => todo!(), RemoteAccessError::InvalidEndpoint => write!(f, "Invalid drop endpoint"), RemoteAccessError::HandshakeFailed => write!(f, "Failed to complete handshake"), RemoteAccessError::GameNotFound => write!(f, "Could not find game on server"), diff --git a/desktop/yarn.lock b/desktop/yarn.lock index da122f89..af475d71 100644 --- a/desktop/yarn.lock +++ b/desktop/yarn.lock @@ -1376,10 +1376,10 @@ dependencies: "@tauri-apps/api" "^2.0.0" -"@tauri-apps/plugin-dialog@~2": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-dialog/-/plugin-dialog-2.0.0.tgz#f1e2840c7f824572a76b375fd1b538a36f28de14" - integrity sha512-ApNkejXP2jpPBSifznPPcHTXxu9/YaRW+eJ+8+nYwqp0lLUtebFHG4QhxitM43wwReHE81WAV1DQ/b+2VBftOA== +"@tauri-apps/plugin-dialog@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-dialog/-/plugin-dialog-2.0.1.tgz#cca38f2ef361c6d92495f5aa12154492cf3fa779" + integrity sha512-fnUrNr6EfvTqdls/ufusU7h6UbNFzLKvHk/zTuOiBq01R3dTODqwctZlzakdbfSp/7pNwTKvgKTAgl/NAP/Z0Q== dependencies: "@tauri-apps/api" "^2.0.0" From aa1f70d3449dd346152af21a7d7d819e6d9c9e74 Mon Sep 17 00:00:00 2001 From: DecDuck Date: Mon, 25 Nov 2024 16:09:29 +1100 Subject: [PATCH 16/31] feat(settings): finish download dir CRUD interface --- desktop/pages/settings/downloads.vue | 33 +++++++++++++++++++++++----- desktop/src-tauri/src/db.rs | 15 ++++++++++++- desktop/src-tauri/src/lib.rs | 5 +++-- 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/desktop/pages/settings/downloads.vue b/desktop/pages/settings/downloads.vue index 0878be79..237ea72c 100644 --- a/desktop/pages/settings/downloads.vue +++ b/desktop/pages/settings/downloads.vue @@ -27,7 +27,7 @@

  • @@ -43,7 +43,16 @@
- @@ -169,7 +178,14 @@ const currentDirectory = ref(undefined); const error = ref(undefined); const createDirectoryLoading = ref(false); -const dirs = ref(await invoke>("fetch_download_dir_stats")); +const dirs = ref>([]); + +async function updateDirs() { + const newDirs = await invoke>("fetch_download_dir_stats"); + dirs.value = newDirs; +} + +await updateDirs(); async function selectDirectoryDialog(): Promise { const res = await invoke("plugin:dialog|open", { @@ -201,12 +217,12 @@ async function submitDirectory() { createDirectoryLoading.value = true; // Add directory - await invoke("add_new_download_dir", { newDir: currentDirectory.value }); + await invoke("add_download_dir", { newDir: currentDirectory.value }); // Update list - const newDirs = await invoke>("fetch_download_dir_stats"); - dirs.value = newDirs; + await updateDirs(); + currentDirectory.value = undefined; createDirectoryLoading.value = false; open.value = false; } catch (e) { @@ -214,4 +230,9 @@ async function submitDirectory() { createDirectoryLoading.value = false; } } + +async function deleteDirectory(index: number) { + await invoke("delete_download_dir", { index }); + await updateDirs(); +} diff --git a/desktop/src-tauri/src/db.rs b/desktop/src-tauri/src/db.rs index c6d32e7e..9ebd141d 100644 --- a/desktop/src-tauri/src/db.rs +++ b/desktop/src-tauri/src/db.rs @@ -101,7 +101,7 @@ impl DatabaseImpls for DatabaseInterface { } #[tauri::command] -pub fn add_new_download_dir(new_dir: String) -> Result<(), String> { +pub fn add_download_dir(new_dir: String) -> Result<(), String> { // Check the new directory is all good let new_dir_path = Path::new(&new_dir); if new_dir_path.exists() { @@ -124,6 +124,9 @@ pub fn add_new_download_dir(new_dir: String) -> Result<(), String> { // Add it to the dictionary let mut lock = DB.borrow_data_mut().unwrap(); + if lock.games.install_dirs.contains(&new_dir) { + return Err("Download directory already used".to_string()); + } lock.games.install_dirs.push(new_dir); drop(lock); DB.save().unwrap(); @@ -131,6 +134,16 @@ pub fn add_new_download_dir(new_dir: String) -> Result<(), String> { Ok(()) } +#[tauri::command] +pub fn delete_download_dir(index: usize) -> Result<(), String> { + let mut lock = DB.borrow_data_mut().unwrap(); + lock.games.install_dirs.remove(index); + drop(lock); + DB.save().unwrap(); + + Ok(()) +} + // Will, in future, return disk/remaining size // Just returns the directories that have been set up #[tauri::command] diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs index 707b787a..06eef38c 100644 --- a/desktop/src-tauri/src/lib.rs +++ b/desktop/src-tauri/src/lib.rs @@ -10,7 +10,7 @@ mod tests; use crate::db::DatabaseImpls; use auth::{auth_initiate, generate_authorization_header, recieve_handshake}; -use db::{add_new_download_dir, fetch_download_dir_stats, DatabaseInterface, DATA_ROOT_DIR}; +use db::{add_download_dir, delete_download_dir, fetch_download_dir_stats, DatabaseInterface, DATA_ROOT_DIR}; use downloads::download_commands::*; use downloads::download_manager::DownloadManagerBuilder; use downloads::download_manager_interface::DownloadManager; @@ -126,7 +126,8 @@ pub fn run() { // Library fetch_library, fetch_game, - add_new_download_dir, + add_download_dir, + delete_download_dir, fetch_download_dir_stats, // Downloads download_game, From e08a2b20f363b78323cd9791fc2334cd64829307 Mon Sep 17 00:00:00 2001 From: DecDuck Date: Tue, 26 Nov 2024 18:09:15 +1100 Subject: [PATCH 17/31] feat: retry connnection on server unavailable --- desktop/pages/error/serverunavailable.vue | 18 ++++++++++++++++-- desktop/src-tauri/src/auth.rs | 18 ++++++++++++++---- desktop/src-tauri/src/lib.rs | 3 ++- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/desktop/pages/error/serverunavailable.vue b/desktop/pages/error/serverunavailable.vue index d0a25159..cfbb1785 100644 --- a/desktop/pages/error/serverunavailable.vue +++ b/desktop/pages/error/serverunavailable.vue @@ -20,9 +20,15 @@ We were unable to contact your Drop instance. See if you can open it in your web browser, or contact your server admin for help.

-
+
+ Connect to different instance @@ -68,7 +74,15 @@ diff --git a/desktop/src-tauri/src/auth.rs b/desktop/src-tauri/src/auth.rs index 78da5b2a..6761cecc 100644 --- a/desktop/src-tauri/src/auth.rs +++ b/desktop/src-tauri/src/auth.rs @@ -1,7 +1,5 @@ use std::{ - env, - sync::Mutex, - time::{SystemTime, UNIX_EPOCH}, + borrow::BorrowMut, env, sync::Mutex, time::{SystemTime, UNIX_EPOCH} }; use log::{info, warn}; @@ -11,7 +9,7 @@ use tauri::{AppHandle, Emitter, Manager}; use url::Url; use crate::{ - db::{DatabaseAuth, DatabaseImpls}, + db::{self, DatabaseAuth, DatabaseImpls}, remote::RemoteAccessError, AppState, AppStatus, User, DB, }; @@ -185,6 +183,18 @@ pub async fn auth_initiate<'a>() -> Result<(), String> { Ok(()) } +#[tauri::command] +pub fn retry_connect(state: tauri::State<'_, Mutex>) -> Result<(), ()> { + let (app_status, user) = setup()?; + + let mut guard = state.lock().unwrap(); + guard.status = app_status; + guard.user = user; + drop(guard); + + Ok(()) +} + pub fn setup() -> Result<(AppStatus, Option), ()> { let data = DB.borrow_data().unwrap(); diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs index 06eef38c..ce9462ee 100644 --- a/desktop/src-tauri/src/lib.rs +++ b/desktop/src-tauri/src/lib.rs @@ -9,7 +9,7 @@ mod settings; mod tests; use crate::db::DatabaseImpls; -use auth::{auth_initiate, generate_authorization_header, recieve_handshake}; +use auth::{auth_initiate, generate_authorization_header, recieve_handshake, retry_connect}; use db::{add_download_dir, delete_download_dir, fetch_download_dir_stats, DatabaseInterface, DATA_ROOT_DIR}; use downloads::download_commands::*; use downloads::download_manager::DownloadManagerBuilder; @@ -120,6 +120,7 @@ pub fn run() { fetch_state, // Auth auth_initiate, + retry_connect, // Remote use_remote, gen_drop_url, From 12b15aa0fd1880aa1ea06fe6ee25ac738c81d1e8 Mon Sep 17 00:00:00 2001 From: DecDuck Date: Tue, 26 Nov 2024 19:54:43 +1100 Subject: [PATCH 18/31] refactor(download manager): rename files to what they contain --- desktop/src-tauri/src/db.rs | 1 - .../src/downloads/download_manager.rs | 345 ++++++------------ .../src/downloads/download_manager_builder.rs | 252 +++++++++++++ .../downloads/download_manager_interface.rs | 135 ------- desktop/src-tauri/src/downloads/mod.rs | 2 +- desktop/src-tauri/src/downloads/queue.rs | 2 +- desktop/src-tauri/src/lib.rs | 12 +- desktop/src-tauri/src/library.rs | 15 + 8 files changed, 398 insertions(+), 366 deletions(-) create mode 100644 desktop/src-tauri/src/downloads/download_manager_builder.rs delete mode 100644 desktop/src-tauri/src/downloads/download_manager_interface.rs diff --git a/desktop/src-tauri/src/db.rs b/desktop/src-tauri/src/db.rs index 9ebd141d..74536dc1 100644 --- a/desktop/src-tauri/src/db.rs +++ b/desktop/src-tauri/src/db.rs @@ -28,7 +28,6 @@ pub enum DatabaseGameStatus { Downloading, Installed, Updating, - Uninstalling, } diff --git a/desktop/src-tauri/src/downloads/download_manager.rs b/desktop/src-tauri/src/downloads/download_manager.rs index d71d801e..f3406aab 100644 --- a/desktop/src-tauri/src/downloads/download_manager.rs +++ b/desktop/src-tauri/src/downloads/download_manager.rs @@ -1,69 +1,20 @@ use std::{ - collections::{HashMap, VecDeque}, + any::Any, + collections::VecDeque, sync::{ - mpsc::{channel, Receiver, Sender}, - Arc, Mutex, + mpsc::{SendError, Sender}, + Arc, Mutex, MutexGuard, }, - thread::spawn, + thread::JoinHandle, }; -use log::{info, warn}; +use log::info; use super::{ download_agent::{GameDownloadAgent, GameDownloadError}, - download_manager_interface::{AgentInterfaceData, DownloadManager}, - download_thread_control_flag::{DownloadThreadControl, DownloadThreadControlFlag}, progress_object::ProgressObject, queue::Queue, }; -/* - -Welcome to the download manager, the most overengineered, glorious piece of bullshit. - -The download manager takes a queue of game_ids and their associated -GameDownloadAgents, and then, one-by-one, executes them. It provides an interface -to interact with the currently downloading agent, and manage the queue. - -When the DownloadManager is initialised, it is designed to provide a reference -which can be used to provide some instructions (the DownloadManagerInterface), -but other than that, it runs without any sort of interruptions. - -It does this by opening up two data structures. Primarily is the command_receiver, -and mpsc (multi-channel-single-producer) which allows commands to be sent from -the Interface, and queued up for the Manager to process. - -These have been mapped in the DownloadManagerSignal docs. - -The other way to interact with the DownloadManager is via the donwload_queue, -which is just a collection of ids which may be rearranged to suit -whichever download queue order is required. - -+----------------------------------------------------------------------------+ -| DO NOT ATTEMPT TO ADD OR REMOVE FROM THE QUEUE WITHOUT USING SIGNALS!! | -| THIS WILL CAUSE A DESYNC BETWEEN THE DOWNLOAD AGENT REGISTRY AND THE QUEUE | -| WHICH HAS NOT BEEN ACCOUNTED FOR | -+----------------------------------------------------------------------------+ - -This download queue does not actually own any of the GameDownloadAgents. It is -simply a id-based reference system. The actual Agents are stored in the -download_agent_registry HashMap, as ordering is no issue here. This is why -appending or removing from the download_queue must be done via signals. - -Behold, my madness - quexeky - -*/ - -pub struct DownloadManagerBuilder { - download_agent_registry: HashMap>, - download_queue: Queue, - command_receiver: Receiver, - sender: Sender, - progress: Arc>>, - status: Arc>, - - current_game_interface: Option>, // Should be the only game download agent in the map with the "Go" flag - active_control_flag: Option, -} pub enum DownloadManagerSignal { /// Resumes (or starts) the DownloadManager Go, @@ -86,7 +37,7 @@ pub enum DownloadManagerStatus { Downloading, Paused, Empty, - Error, + Error(GameDownloadError), } pub enum GameDownloadStatus { Downloading, @@ -95,174 +46,120 @@ pub enum GameDownloadStatus { Error, } -impl DownloadManagerBuilder { - pub fn build() -> DownloadManager { - let queue = Queue::new(); - let (command_sender, command_receiver) = channel(); - let active_progress = Arc::new(Mutex::new(None)); - let status = Arc::new(Mutex::new(DownloadManagerStatus::Empty)); - - let manager = Self { - download_agent_registry: HashMap::new(), - download_queue: queue.clone(), - command_receiver, - current_game_interface: None, - active_control_flag: None, - status: status.clone(), - sender: command_sender.clone(), - progress: active_progress.clone(), - }; - - let terminator = spawn(|| manager.manage_queue()); - - DownloadManager::new(terminator, queue, active_progress, command_sender) - } - - fn manage_queue(mut self) -> Result<(), ()> { - loop { - let signal = match self.command_receiver.recv() { - Ok(signal) => signal, - Err(e) => return Err(()), - }; - - match signal { - DownloadManagerSignal::Go => { - self.manage_go_signal(); - } - DownloadManagerSignal::Stop => { - self.manage_stop_signal(); - } - DownloadManagerSignal::Completed(game_id) => { - self.manage_completed_signal(game_id); - } - DownloadManagerSignal::Queue(game_id, version, target_download_dir) => { - self.manage_queue_signal(game_id, version, target_download_dir); - } - DownloadManagerSignal::Finish => { - if let Some(active_control_flag) = self.active_control_flag { - active_control_flag.set(DownloadThreadControlFlag::Stop) - } - return Ok(()); - } - DownloadManagerSignal::Error(e) => { - self.manage_error_signal(e); - }, - DownloadManagerSignal::Cancel(id) => { - self.manage_cancel_signal(id); - }, - }; +/// Accessible front-end for the DownloadManager +/// +/// The system works entirely through signals, both internally and externally, +/// all of which are accessible through the DownloadManagerSignal type, but +/// should not be used directly. Rather, signals are abstracted through this +/// interface. +/// +/// The actual download queue may be accessed through the .edit() function, +/// which provides raw access to the underlying queue. +/// THIS EDITING IS BLOCKING!!! +pub struct DownloadManager { + terminator: JoinHandle>, + download_queue: Queue, + progress: Arc>>, + command_sender: Sender, +} +pub struct AgentInterfaceData { + pub id: String, + pub status: Mutex, +} +impl From> for AgentInterfaceData { + fn from(value: Arc) -> Self { + Self { + id: value.id.clone(), + status: Mutex::from(GameDownloadStatus::Uninitialised), } } - - fn manage_stop_signal(&mut self) { - info!("Got signal 'Stop'"); - if let Some(active_control_flag) = self.active_control_flag.clone() { - active_control_flag.set(DownloadThreadControlFlag::Stop); - } - } - - fn manage_completed_signal(&mut self, game_id: String) { - info!("Got signal 'Completed'"); - if let Some(interface) = &self.current_game_interface { - // When if let chains are stabilised, combine these two statements - if interface.id == game_id { - info!("Popping consumed data"); - self.download_queue.pop_front(); - self.download_agent_registry.remove(&game_id); - self.active_control_flag = None; - *self.progress.lock().unwrap() = None; - } - } - self.sender.send(DownloadManagerSignal::Go).unwrap(); - } - - fn manage_queue_signal(&mut self, id: String, version: String, target_download_dir: usize) { - info!("Got signal Queue"); - let download_agent = Arc::new(GameDownloadAgent::new( - id.clone(), - version, - target_download_dir, - self.sender.clone() - )); - let agent_status = GameDownloadStatus::Uninitialised; - let interface_data = AgentInterfaceData { - id, - status: Mutex::new(agent_status), - }; - self.download_agent_registry - .insert(interface_data.id.clone(), download_agent); - self.download_queue.append(interface_data); - } - - fn manage_go_signal(&mut self) { - info!("Got signal 'Go'"); - if self.active_control_flag.is_none() && !self.download_agent_registry.is_empty() { - info!("Starting download agent"); - let download_agent = { - let front = self.download_queue.read().front().unwrap().clone(); - self.download_agent_registry - .get(&front.id) - .unwrap() - .clone() - }; - let download_agent_interface = - Arc::new(AgentInterfaceData::from(download_agent.clone())); - self.current_game_interface = Some(download_agent_interface); - - let progress_object = download_agent.progress.clone(); - *self.progress.lock().unwrap() = Some(progress_object); - - let active_control_flag = download_agent.control_flag.clone(); - self.active_control_flag = Some(active_control_flag.clone()); - - let sender = self.sender.clone(); - - info!("Spawning download"); - spawn(move || { - match download_agent.download() { - Ok(_) => { - sender.send(DownloadManagerSignal::Completed(download_agent.id.clone())).unwrap(); - }, - Err(e) => { - warn!("Download failed"); - //todo!() // Account for if the setup_download function fails - }, - }; - }); - info!("Finished spawning Download"); - - active_control_flag.set(DownloadThreadControlFlag::Go); - self.set_status(DownloadManagerStatus::Downloading); - } else if let Some(active_control_flag) = self.active_control_flag.clone() { - info!("Restarting current download"); - active_control_flag.set(DownloadThreadControlFlag::Go); - } else { - info!("Nothing was set"); - } - } - fn manage_error_signal(&self, error: GameDownloadError) { - let current_status = self.current_game_interface.clone().unwrap(); - let mut lock = current_status.status.lock().unwrap(); - *lock = GameDownloadStatus::Error; - self.set_status(DownloadManagerStatus::Error); - } - fn manage_cancel_signal(&mut self, game_id: String) { - if let Some(current_flag) = &self.active_control_flag { - current_flag.set(DownloadThreadControlFlag::Stop); - self.active_control_flag = None; - *self.progress.lock().unwrap() = None; - } - self.download_agent_registry.remove(&game_id); - let mut lock = self.download_queue.edit(); - let index = match lock.iter().position(|interface| interface.id == game_id) { - Some(index) => index, - None => return, - }; - lock.remove(index); - self.sender.send(DownloadManagerSignal::Go).unwrap(); - info!("{:?}", self.download_agent_registry.iter().map(|x| x.0.clone()).collect::()); - } - fn set_status(&self, status: DownloadManagerStatus) { - *self.status.lock().unwrap() = status; - } +} + +impl DownloadManager { + pub fn new( + terminator: JoinHandle>, + download_queue: Queue, + progress: Arc>>, + command_sender: Sender, + ) -> Self { + Self { + terminator, + download_queue, + progress, + command_sender, + } + } + + pub fn queue_game( + &self, + id: String, + version: String, + target_download_dir: usize, + ) -> Result<(), SendError> { + info!("Adding game id {}", id); + self.command_sender.send(DownloadManagerSignal::Queue( + id, + version, + target_download_dir, + ))?; + self.command_sender.send(DownloadManagerSignal::Go) + } + pub fn cancel_download( + &self, + game_id: String + ) { + self.command_sender.send(DownloadManagerSignal::Cancel(game_id)).unwrap(); + } + pub fn edit(&self) -> MutexGuard<'_, VecDeque>> { + self.download_queue.edit() + } + pub fn read_queue(&self) -> VecDeque> { + self.download_queue.read() + } + pub fn get_current_game_download_progress(&self) -> Option { + let progress_object = (*self.progress.lock().unwrap()).clone()?; + Some(progress_object.get_progress()) + } + pub fn rearrange_string(&self, id: String, new_index: usize) { + let mut queue = self.edit(); + let current_index = get_index_from_id(&mut queue, id).unwrap(); + let to_move = queue.remove(current_index).unwrap(); + queue.insert(new_index, to_move); + } + pub fn rearrange(&self, current_index: usize, new_index: usize) { + let mut queue = self.edit(); + let to_move = queue.remove(current_index).unwrap(); + queue.insert(new_index, to_move); + } + pub fn remove_from_queue(&self, index: usize) { + self.edit().remove(index); + } + pub fn remove_from_queue_string(&self, id: String) { + let mut queue = self.edit(); + let current_index = get_index_from_id(&mut queue, id).unwrap(); + queue.remove(current_index); + } + pub fn pause_downloads(&self) -> Result<(), SendError> { + self.command_sender.send(DownloadManagerSignal::Stop) + } + pub fn resume_downloads(&self) -> Result<(), SendError> { + self.command_sender.send(DownloadManagerSignal::Go) + } + pub fn ensure_terminated(self) -> Result, Box> { + self.command_sender + .send(DownloadManagerSignal::Finish) + .unwrap(); + self.terminator.join() + } +} + +/// Takes in the locked value from .edit() and attempts to +/// get the index of whatever game_id is passed in +fn get_index_from_id( + queue: &mut MutexGuard<'_, VecDeque>>, + id: String, +) -> Option { + queue + .iter() + .position(|download_agent| download_agent.id == id) } diff --git a/desktop/src-tauri/src/downloads/download_manager_builder.rs b/desktop/src-tauri/src/downloads/download_manager_builder.rs new file mode 100644 index 00000000..e696abf4 --- /dev/null +++ b/desktop/src-tauri/src/downloads/download_manager_builder.rs @@ -0,0 +1,252 @@ +use std::{ + collections::HashMap, + sync::{ + mpsc::{channel, Receiver, Sender}, + Arc, Mutex, + }, + thread::spawn, +}; + +use log::{error, info, warn}; + +use super::{ + download_agent::{GameDownloadAgent, GameDownloadError}, + download_manager::{ + AgentInterfaceData, DownloadManager, DownloadManagerSignal, DownloadManagerStatus, + GameDownloadStatus, + }, + download_thread_control_flag::{DownloadThreadControl, DownloadThreadControlFlag}, + progress_object::ProgressObject, + queue::Queue, +}; + +/* + +Welcome to the download manager, the most overengineered, glorious piece of bullshit. + +The download manager takes a queue of game_ids and their associated +GameDownloadAgents, and then, one-by-one, executes them. It provides an interface +to interact with the currently downloading agent, and manage the queue. + +When the DownloadManager is initialised, it is designed to provide a reference +which can be used to provide some instructions (the DownloadManagerInterface), +but other than that, it runs without any sort of interruptions. + +It does this by opening up two data structures. Primarily is the command_receiver, +and mpsc (multi-channel-single-producer) which allows commands to be sent from +the Interface, and queued up for the Manager to process. + +These have been mapped in the DownloadManagerSignal docs. + +The other way to interact with the DownloadManager is via the donwload_queue, +which is just a collection of ids which may be rearranged to suit +whichever download queue order is required. + ++----------------------------------------------------------------------------+ +| DO NOT ATTEMPT TO ADD OR REMOVE FROM THE QUEUE WITHOUT USING SIGNALS!! | +| THIS WILL CAUSE A DESYNC BETWEEN THE DOWNLOAD AGENT REGISTRY AND THE QUEUE | +| WHICH HAS NOT BEEN ACCOUNTED FOR | ++----------------------------------------------------------------------------+ + +This download queue does not actually own any of the GameDownloadAgents. It is +simply a id-based reference system. The actual Agents are stored in the +download_agent_registry HashMap, as ordering is no issue here. This is why +appending or removing from the download_queue must be done via signals. + +Behold, my madness - quexeky + +*/ + +pub struct DownloadManagerBuilder { + download_agent_registry: HashMap>, + download_queue: Queue, + command_receiver: Receiver, + sender: Sender, + progress: Arc>>, + status: Arc>, + + current_game_interface: Option>, // Should be the only game download agent in the map with the "Go" flag + active_control_flag: Option, +} + +impl DownloadManagerBuilder { + pub fn build() -> DownloadManager { + let queue = Queue::new(); + let (command_sender, command_receiver) = channel(); + let active_progress = Arc::new(Mutex::new(None)); + let status = Arc::new(Mutex::new(DownloadManagerStatus::Empty)); + + let manager = Self { + download_agent_registry: HashMap::new(), + download_queue: queue.clone(), + command_receiver, + current_game_interface: None, + active_control_flag: None, + status: status.clone(), + sender: command_sender.clone(), + progress: active_progress.clone(), + }; + + let terminator = spawn(|| manager.manage_queue()); + + DownloadManager::new(terminator, queue, active_progress, command_sender) + } + + fn manage_queue(mut self) -> Result<(), ()> { + loop { + let signal = match self.command_receiver.recv() { + Ok(signal) => signal, + Err(_) => return Err(()), + }; + + match signal { + DownloadManagerSignal::Go => { + self.manage_go_signal(); + } + DownloadManagerSignal::Stop => { + self.manage_stop_signal(); + } + DownloadManagerSignal::Completed(game_id) => { + self.manage_completed_signal(game_id); + } + DownloadManagerSignal::Queue(game_id, version, target_download_dir) => { + self.manage_queue_signal(game_id, version, target_download_dir); + } + DownloadManagerSignal::Finish => { + if let Some(active_control_flag) = self.active_control_flag { + active_control_flag.set(DownloadThreadControlFlag::Stop) + } + return Ok(()); + } + DownloadManagerSignal::Error(e) => { + self.manage_error_signal(e); + } + DownloadManagerSignal::Cancel(id) => { + self.manage_cancel_signal(id); + } + }; + } + } + + fn manage_stop_signal(&mut self) { + info!("Got signal 'Stop'"); + if let Some(active_control_flag) = self.active_control_flag.clone() { + active_control_flag.set(DownloadThreadControlFlag::Stop); + } + } + + fn manage_completed_signal(&mut self, game_id: String) { + info!("Got signal 'Completed'"); + if let Some(interface) = &self.current_game_interface { + // When if let chains are stabilised, combine these two statements + if interface.id == game_id { + info!("Popping consumed data"); + self.download_queue.pop_front(); + self.download_agent_registry.remove(&game_id); + self.active_control_flag = None; + *self.progress.lock().unwrap() = None; + } + } + self.sender.send(DownloadManagerSignal::Go).unwrap(); + } + + fn manage_queue_signal(&mut self, id: String, version: String, target_download_dir: usize) { + info!("Got signal Queue"); + let download_agent = Arc::new(GameDownloadAgent::new( + id.clone(), + version, + target_download_dir, + self.sender.clone(), + )); + let agent_status = GameDownloadStatus::Uninitialised; + let interface_data = AgentInterfaceData { + id, + status: Mutex::new(agent_status), + }; + self.download_agent_registry + .insert(interface_data.id.clone(), download_agent); + self.download_queue.append(interface_data); + } + + fn manage_go_signal(&mut self) { + info!("Got signal 'Go'"); + if self.active_control_flag.is_none() && !self.download_agent_registry.is_empty() { + info!("Starting download agent"); + let download_agent = { + let front = self.download_queue.read().front().unwrap().clone(); + self.download_agent_registry.get(&front.id).unwrap().clone() + }; + let download_agent_interface = + Arc::new(AgentInterfaceData::from(download_agent.clone())); + self.current_game_interface = Some(download_agent_interface); + + let progress_object = download_agent.progress.clone(); + *self.progress.lock().unwrap() = Some(progress_object); + + let active_control_flag = download_agent.control_flag.clone(); + self.active_control_flag = Some(active_control_flag.clone()); + + let sender = self.sender.clone(); + + info!("Spawning download"); + spawn(move || { + match download_agent.download() { + Ok(_) => { + // TODO wrap this pattern in a macro + let result = sender + .send(DownloadManagerSignal::Completed(download_agent.id.clone())); + if let Err(err) = result { + error!("{}", err); + } + } + Err(err) => { + let result = sender.send(DownloadManagerSignal::Error(err)); + if let Err(err) = result { + error!("{}", err); + } + } + }; + }); + info!("Finished spawning Download"); + + active_control_flag.set(DownloadThreadControlFlag::Go); + self.set_status(DownloadManagerStatus::Downloading); + } else if let Some(active_control_flag) = self.active_control_flag.clone() { + info!("Restarting current download"); + active_control_flag.set(DownloadThreadControlFlag::Go); + } else { + info!("Nothing was set"); + } + } + fn manage_error_signal(&self, error: GameDownloadError) { + let current_status = self.current_game_interface.clone().unwrap(); + let mut lock = current_status.status.lock().unwrap(); + *lock = GameDownloadStatus::Error; + self.set_status(DownloadManagerStatus::Error(error)); + } + fn manage_cancel_signal(&mut self, game_id: String) { + if let Some(current_flag) = &self.active_control_flag { + current_flag.set(DownloadThreadControlFlag::Stop); + self.active_control_flag = None; + *self.progress.lock().unwrap() = None; + } + self.download_agent_registry.remove(&game_id); + let mut lock = self.download_queue.edit(); + let index = match lock.iter().position(|interface| interface.id == game_id) { + Some(index) => index, + None => return, + }; + lock.remove(index); + self.sender.send(DownloadManagerSignal::Go).unwrap(); + info!( + "{:?}", + self.download_agent_registry + .iter() + .map(|x| x.0.clone()) + .collect::() + ); + } + fn set_status(&self, status: DownloadManagerStatus) { + *self.status.lock().unwrap() = status; + } +} diff --git a/desktop/src-tauri/src/downloads/download_manager_interface.rs b/desktop/src-tauri/src/downloads/download_manager_interface.rs deleted file mode 100644 index 0ecf2bd9..00000000 --- a/desktop/src-tauri/src/downloads/download_manager_interface.rs +++ /dev/null @@ -1,135 +0,0 @@ -use std::{ - any::Any, - collections::VecDeque, - sync::{ - mpsc::{SendError, Sender}, - Arc, Mutex, MutexGuard, - }, - thread::JoinHandle, -}; - -use log::info; - -use super::{ - download_agent::GameDownloadAgent, - download_manager::{DownloadManagerSignal, GameDownloadStatus}, - progress_object::ProgressObject, queue::Queue, -}; - -/// Accessible front-end for the DownloadManager -/// -/// The system works entirely through signals, both internally and externally, -/// all of which are accessible through the DownloadManagerSignal type, but -/// should not be used directly. Rather, signals are abstracted through this -/// interface. -/// -/// The actual download queue may be accessed through the .edit() function, -/// which provides raw access to the underlying queue. -/// THIS EDITING IS BLOCKING!!! -pub struct DownloadManager { - terminator: JoinHandle>, - download_queue: Queue, - progress: Arc>>, - command_sender: Sender, -} -pub struct AgentInterfaceData { - pub id: String, - pub status: Mutex, -} -impl From> for AgentInterfaceData { - fn from(value: Arc) -> Self { - Self { - id: value.id.clone(), - status: Mutex::from(GameDownloadStatus::Uninitialised), - } - } -} - -impl DownloadManager { - pub fn new( - terminator: JoinHandle>, - download_queue: Queue, - progress: Arc>>, - command_sender: Sender, - ) -> Self { - Self { - terminator, - download_queue, - progress, - command_sender, - } - } - - pub fn queue_game( - &self, - id: String, - version: String, - target_download_dir: usize, - ) -> Result<(), SendError> { - info!("Adding game id {}", id); - self.command_sender.send(DownloadManagerSignal::Queue( - id, - version, - target_download_dir, - ))?; - self.command_sender.send(DownloadManagerSignal::Go) - } - pub fn cancel_download( - &self, - game_id: String - ) { - self.command_sender.send(DownloadManagerSignal::Cancel(game_id)).unwrap(); - } - pub fn edit(&self) -> MutexGuard<'_, VecDeque>> { - self.download_queue.edit() - } - pub fn read_queue(&self) -> VecDeque> { - self.download_queue.read() - } - pub fn get_current_game_download_progress(&self) -> Option { - let progress_object = (*self.progress.lock().unwrap()).clone()?; - Some(progress_object.get_progress()) - } - pub fn rearrange_string(&self, id: String, new_index: usize) { - let mut queue = self.edit(); - let current_index = get_index_from_id(&mut queue, id).unwrap(); - let to_move = queue.remove(current_index).unwrap(); - queue.insert(new_index, to_move); - } - pub fn rearrange(&self, current_index: usize, new_index: usize) { - let mut queue = self.edit(); - let to_move = queue.remove(current_index).unwrap(); - queue.insert(new_index, to_move); - } - pub fn remove_from_queue(&self, index: usize) { - self.edit().remove(index); - } - pub fn remove_from_queue_string(&self, id: String) { - let mut queue = self.edit(); - let current_index = get_index_from_id(&mut queue, id).unwrap(); - queue.remove(current_index); - } - pub fn pause_downloads(&self) -> Result<(), SendError> { - self.command_sender.send(DownloadManagerSignal::Stop) - } - pub fn resume_downloads(&self) -> Result<(), SendError> { - self.command_sender.send(DownloadManagerSignal::Go) - } - pub fn ensure_terminated(self) -> Result, Box> { - self.command_sender - .send(DownloadManagerSignal::Finish) - .unwrap(); - self.terminator.join() - } -} - -/// Takes in the locked value from .edit() and attempts to -/// get the index of whatever game_id is passed in -fn get_index_from_id( - queue: &mut MutexGuard<'_, VecDeque>>, - id: String, -) -> Option { - queue - .iter() - .position(|download_agent| download_agent.id == id) -} diff --git a/desktop/src-tauri/src/downloads/mod.rs b/desktop/src-tauri/src/downloads/mod.rs index 3556f81b..c7c11eea 100644 --- a/desktop/src-tauri/src/downloads/mod.rs +++ b/desktop/src-tauri/src/downloads/mod.rs @@ -1,8 +1,8 @@ pub mod download_agent; pub mod download_commands; mod download_logic; +pub mod download_manager_builder; pub mod download_manager; -pub mod download_manager_interface; mod download_thread_control_flag; mod manifest; mod progress_object; diff --git a/desktop/src-tauri/src/downloads/queue.rs b/desktop/src-tauri/src/downloads/queue.rs index de1be177..1ce695ff 100644 --- a/desktop/src-tauri/src/downloads/queue.rs +++ b/desktop/src-tauri/src/downloads/queue.rs @@ -1,6 +1,6 @@ use std::{collections::VecDeque, sync::{Arc, Mutex, MutexGuard}}; -use super::download_manager_interface::AgentInterfaceData; +use super::download_manager::AgentInterfaceData; #[derive(Clone)] pub struct Queue { diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs index ce9462ee..840b8033 100644 --- a/desktop/src-tauri/src/lib.rs +++ b/desktop/src-tauri/src/lib.rs @@ -10,13 +10,16 @@ mod tests; use crate::db::DatabaseImpls; use auth::{auth_initiate, generate_authorization_header, recieve_handshake, retry_connect}; -use db::{add_download_dir, delete_download_dir, fetch_download_dir_stats, DatabaseInterface, DATA_ROOT_DIR}; +use db::{ + add_download_dir, delete_download_dir, fetch_download_dir_stats, DatabaseInterface, + DATA_ROOT_DIR, +}; use downloads::download_commands::*; -use downloads::download_manager::DownloadManagerBuilder; -use downloads::download_manager_interface::DownloadManager; +use downloads::download_manager_builder::DownloadManagerBuilder; +use downloads::download_manager::DownloadManager; use env_logger::Env; use http::{header::*, response::Builder as ResponseBuilder}; -use library::{fetch_game, fetch_library, Game}; +use library::{fetch_game, fetch_game_status, fetch_library, Game}; use log::{debug, info}; use remote::{gen_drop_url, use_remote, RemoteAccessError}; use serde::{Deserialize, Serialize}; @@ -130,6 +133,7 @@ pub fn run() { add_download_dir, delete_download_dir, fetch_download_dir_stats, + fetch_game_status, // Downloads download_game, get_current_game_download_progress, diff --git a/desktop/src-tauri/src/library.rs b/desktop/src-tauri/src/library.rs index 50da6af6..3fbea718 100644 --- a/desktop/src-tauri/src/library.rs +++ b/desktop/src-tauri/src/library.rs @@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize}; use serde_json::json; use tauri::{AppHandle, Manager}; +use crate::db; use crate::db::DatabaseGameStatus; use crate::db::DatabaseImpls; use crate::remote::RemoteAccessError; @@ -109,3 +110,17 @@ pub fn fetch_game(id: String, app: tauri::AppHandle) -> Result { Ok(result.unwrap()) } + +#[tauri::command] +pub fn fetch_game_status(id: String) -> Result { + let db_handle = DB.borrow_data().unwrap(); + let status = db_handle + .games + .games_statuses + .get(&id) + .unwrap_or(&DatabaseGameStatus::Remote) + .clone(); + drop(db_handle); + + return Ok(status); +} From cd42466cc42306efe4d2e9f1d9e01cac1a6fad31 Mon Sep 17 00:00:00 2001 From: DecDuck Date: Tue, 26 Nov 2024 20:11:03 +1100 Subject: [PATCH 19/31] feat(library): automatically fetch remote data if not available --- desktop/pages/library.vue | 2 +- desktop/src-tauri/src/db.rs | 1 + .../src-tauri/src/downloads/download_agent.rs | 2 +- desktop/src-tauri/src/library.rs | 51 +++++++++++++++++-- 4 files changed, 50 insertions(+), 6 deletions(-) diff --git a/desktop/pages/library.vue b/desktop/pages/library.vue index dcb0b849..c91ea64d 100644 --- a/desktop/pages/library.vue +++ b/desktop/pages/library.vue @@ -13,7 +13,7 @@ >
diff --git a/desktop/src-tauri/src/db.rs b/desktop/src-tauri/src/db.rs index 74536dc1..66d08b5b 100644 --- a/desktop/src-tauri/src/db.rs +++ b/desktop/src-tauri/src/db.rs @@ -35,6 +35,7 @@ pub enum DatabaseGameStatus { #[serde(rename_all = "camelCase")] pub struct DatabaseGames { pub install_dirs: Vec, + // Guaranteed to exist if the game also exists in the app state map pub games_statuses: HashMap, } diff --git a/desktop/src-tauri/src/downloads/download_agent.rs b/desktop/src-tauri/src/downloads/download_agent.rs index 0bb3b71c..9987436a 100644 --- a/desktop/src-tauri/src/downloads/download_agent.rs +++ b/desktop/src-tauri/src/downloads/download_agent.rs @@ -212,7 +212,7 @@ impl GameDownloadAgent { } pub fn run(&self) -> Result<(), ()> { - const DOWNLOAD_MAX_THREADS: usize = 4; + const DOWNLOAD_MAX_THREADS: usize = 1; let pool = ThreadPoolBuilder::new() .num_threads(DOWNLOAD_MAX_THREADS) diff --git a/desktop/src-tauri/src/library.rs b/desktop/src-tauri/src/library.rs index 3fbea718..64372b8c 100644 --- a/desktop/src-tauri/src/library.rs +++ b/desktop/src-tauri/src/library.rs @@ -1,3 +1,4 @@ +use std::fmt::format; use std::sync::Mutex; use log::info; @@ -8,6 +9,7 @@ use tauri::{AppHandle, Manager}; use crate::db; use crate::db::DatabaseGameStatus; use crate::db::DatabaseImpls; +use crate::downloads::download_manager::GameDownloadStatus; use crate::remote::RemoteAccessError; use crate::{auth::generate_authorization_header, AppState, DB}; @@ -77,9 +79,9 @@ pub fn fetch_library(app: AppHandle) -> Result { fn fetch_game_logic(id: String, app: tauri::AppHandle) -> Result { let state = app.state::>(); - let handle = state.lock().unwrap(); + let mut state_handle = state.lock().unwrap(); - let game = handle.games.get(&id); + let game = state_handle.games.get(&id); if let Some(game) = game { let db_handle = DB.borrow_data().unwrap(); @@ -95,9 +97,50 @@ fn fetch_game_logic(id: String, app: tauri::AppHandle) -> Result()?; + state_handle.games.insert(id.clone(), game.clone()); + + let mut db_handle = DB.borrow_data_mut().unwrap(); + + if !db_handle.games.games_statuses.contains_key(&id) { + db_handle + .games + .games_statuses + .insert(id, DatabaseGameStatus::Remote); + } + + let data = FetchGameStruct { + game: game.clone(), + status: db_handle + .games + .games_statuses + .get(&game.id) + .unwrap() + .clone(), + }; + + return Ok(json!(data).to_string()); } #[tauri::command] From 95109899b723273cb6b722305d769f36966a8429 Mon Sep 17 00:00:00 2001 From: DecDuck Date: Thu, 28 Nov 2024 12:39:21 +1100 Subject: [PATCH 20/31] fix(download manager): use of completed signal, and pause/resuming --- desktop/pages/store/index.vue | 28 ++++- .../src-tauri/src/downloads/download_agent.rs | 102 ++++++++++++------ .../src/downloads/download_commands.rs | 13 ++- .../src-tauri/src/downloads/download_logic.rs | 1 - .../src/downloads/download_manager.rs | 25 ++--- .../src/downloads/download_manager_builder.rs | 43 ++++---- .../src/downloads/progress_object.rs | 13 ++- desktop/src-tauri/src/downloads/queue.rs | 21 ++-- desktop/src-tauri/src/lib.rs | 6 +- 9 files changed, 162 insertions(+), 90 deletions(-) diff --git a/desktop/pages/store/index.vue b/desktop/pages/store/index.vue index 45170b5b..85d220b4 100644 --- a/desktop/pages/store/index.vue +++ b/desktop/pages/store/index.vue @@ -7,14 +7,30 @@ @click="startGameDownload" > Download game - ({{ Math.floor(progress * 1000) / 10 }}%) + + ({{ Math.floor(progress * 1000) / 10 }}%) + + + + + + diff --git a/desktop/src-tauri/src/downloads/download_agent.rs b/desktop/src-tauri/src/downloads/download_agent.rs index 9987436a..2e960e34 100644 --- a/desktop/src-tauri/src/downloads/download_agent.rs +++ b/desktop/src-tauri/src/downloads/download_agent.rs @@ -29,7 +29,7 @@ pub struct GameDownloadAgent { contexts: Mutex>, pub manifest: Mutex>, pub progress: ProgressObject, - sender: Sender + sender: Sender, } #[derive(Debug)] @@ -39,12 +39,12 @@ pub enum GameDownloadError { Setup(SetupError), Lock, IoError(io::Error), - DownloadError + DownloadError, } #[derive(Debug)] pub enum SetupError { - Context + Context, } impl Display for GameDownloadError { @@ -61,7 +61,12 @@ impl Display for GameDownloadError { } impl GameDownloadAgent { - pub fn new(id: String, version: String, target_download_dir: usize, sender: Sender) -> Self { + pub fn new( + id: String, + version: String, + target_download_dir: usize, + sender: Sender, + ) -> Self { // Don't run by default let control_flag = DownloadThreadControl::new(DownloadThreadControlFlag::Stop); Self { @@ -72,7 +77,7 @@ impl GameDownloadAgent { target_download_dir, contexts: Mutex::new(Vec::new()), progress: ProgressObject::new(0, 0), - sender + sender, } } @@ -81,8 +86,8 @@ impl GameDownloadAgent { self.ensure_manifest_exists()?; info!("Ensured manifest exists"); - self.generate_contexts()?; - info!("Generated contexts"); + self.ensure_contexts()?; + info!("Ensured contexts exists"); self.control_flag.set(DownloadThreadControlFlag::Go); @@ -129,7 +134,10 @@ impl GameDownloadAgent { if response.status() != 200 { return Err(GameDownloadError::Communication( - RemoteAccessError::ManifestDownloadFailed(response.status(), response.text().unwrap()) + RemoteAccessError::ManifestDownloadFailed( + response.status(), + response.text().unwrap(), + ), )); } @@ -144,12 +152,15 @@ impl GameDownloadAgent { } fn set_progress_object_params(&self) { + // Avoid re-setting it + if self.progress.get_max() != 0 { + return; + } + let lock = self.contexts.lock().unwrap(); let length = lock.len(); - let chunk_count = lock.iter() - .map(|chunk| chunk.length) - .sum(); + let chunk_count = lock.iter().map(|chunk| chunk.length).sum(); debug!("Setting ProgressObject max to {}", chunk_count); self.progress.set_max(chunk_count); @@ -159,6 +170,18 @@ impl GameDownloadAgent { self.progress.set_time_now(); } + pub fn ensure_contexts(&self) -> Result<(), GameDownloadError> { + let context_lock = self.contexts.lock().unwrap(); + info!("{:?} {}", context_lock, context_lock.is_empty()); + if !context_lock.is_empty() { + return Ok(()); + } + drop(context_lock); + + self.generate_contexts()?; + return Ok(()); + } + pub fn generate_contexts(&self) -> Result<(), GameDownloadError> { let db_lock = DB.borrow_data().unwrap(); let data_base_dir = db_lock.games.install_dirs[self.target_download_dir].clone(); @@ -192,14 +215,14 @@ impl GameDownloadAgent { game_id: game_id.to_string(), path: path.clone(), checksum: chunk.checksums[i].clone(), - length: *length + length: *length, }); running_offset += *length as u64; } #[cfg(target_os = "linux")] if running_offset > 0 { - fallocate(file, FallocateFlags::empty(), 0, running_offset).unwrap(); + let _ = fallocate(file, FallocateFlags::empty(), 0, running_offset); } } @@ -212,54 +235,65 @@ impl GameDownloadAgent { } pub fn run(&self) -> Result<(), ()> { - const DOWNLOAD_MAX_THREADS: usize = 1; + info!("downloading game: {}", self.id); + const DOWNLOAD_MAX_THREADS: usize = 4; let pool = ThreadPoolBuilder::new() .num_threads(DOWNLOAD_MAX_THREADS) .build() .unwrap(); - let new_contexts = Arc::new(Mutex::new(Vec::new())); - let new_contexts_ref = new_contexts.clone(); + let completed_indexes = Arc::new(Mutex::new(Vec::new())); + let completed_indexes_loop_arc = completed_indexes.clone(); pool.scope(move |scope| { let contexts = self.contexts.lock().unwrap(); - for (index, context) in contexts.iter().enumerate() { let context = context.clone(); let control_flag = self.control_flag.clone(); // Clone arcs let progress = self.progress.get(index); // Clone arcs - let new_contexts_ref = new_contexts_ref.clone(); + let completed_indexes_ref = completed_indexes_loop_arc.clone(); scope.spawn(move |_| { - info!( - "starting download for file {} {}", - context.file_name, context.index - ); match download_game_chunk(context.clone(), control_flag, progress) { - Ok(res) => { - match res { - true => {}, - false => new_contexts_ref.lock().unwrap().push(context), + Ok(res) => match res { + true => { + let mut lock = completed_indexes_ref.lock().unwrap(); + lock.push(index); } + false => {} }, Err(e) => { error!("GameDownloadError: {}", e); self.sender.send(DownloadManagerSignal::Error(e)).unwrap(); - new_contexts_ref.lock().unwrap().push(context); - }, + } } }); } }); - if !new_contexts.lock().unwrap().is_empty() { - debug!("New contexts not empty"); - *self.contexts.lock().unwrap() = Arc::into_inner(new_contexts).unwrap().into_inner().unwrap(); - debug!("Contexts: {:?}", *self.contexts.lock().unwrap()); - return Err(()) + + let mut context_lock = self.contexts.lock().unwrap(); + let mut completed_lock = completed_indexes.lock().unwrap(); + + // Sort desc so we don't have to modify indexes + completed_lock.sort_by(|a, b| b.cmp(a)); + + for index in completed_lock.iter() { + context_lock.remove(*index); } - info!("Contexts: {:?}", *self.contexts.lock().unwrap()); + + // If we're not out of contexts, we're not done, so we don't fire completed + if !context_lock.is_empty() { + info!("Download agent didn't finish, not sending completed signal"); + return Ok(()); + } + + // We've completed + self.sender + .send(DownloadManagerSignal::Completed(self.id.clone())) + .unwrap(); + Ok(()) } } diff --git a/desktop/src-tauri/src/downloads/download_commands.rs b/desktop/src-tauri/src/downloads/download_commands.rs index 56441067..c6f3c426 100644 --- a/desktop/src-tauri/src/downloads/download_commands.rs +++ b/desktop/src-tauri/src/downloads/download_commands.rs @@ -34,7 +34,7 @@ pub fn get_current_game_download_progress( } #[tauri::command] -pub fn stop_game_download(state: tauri::State<'_, Mutex>, game_id: String) { +pub fn cancel_game_download(state: tauri::State<'_, Mutex>, game_id: String) { info!("Cancelling game download {}", game_id); state .lock() @@ -42,6 +42,17 @@ pub fn stop_game_download(state: tauri::State<'_, Mutex>, game_id: Str .download_manager .cancel_download(game_id); } + +#[tauri::command] +pub fn pause_game_downloads(state: tauri::State<'_, Mutex>) { + state.lock().unwrap().download_manager.pause_downloads() +} + +#[tauri::command] +pub fn resume_game_downloads(state: tauri::State<'_, Mutex>) { + state.lock().unwrap().download_manager.resume_downloads() +} + #[tauri::command] pub fn get_current_write_speed(state: tauri::State<'_, Mutex>) {} diff --git a/desktop/src-tauri/src/downloads/download_logic.rs b/desktop/src-tauri/src/downloads/download_logic.rs index 942423a7..ed2645f6 100644 --- a/desktop/src-tauri/src/downloads/download_logic.rs +++ b/desktop/src-tauri/src/downloads/download_logic.rs @@ -124,7 +124,6 @@ pub fn download_game_chunk( ) -> Result { // If we're paused if control_flag.get() == DownloadThreadControlFlag::Stop { - info!("Control flag is Stop"); progress.store(0, Ordering::Relaxed); return Ok(false); } diff --git a/desktop/src-tauri/src/downloads/download_manager.rs b/desktop/src-tauri/src/downloads/download_manager.rs index f3406aab..f57c87c2 100644 --- a/desktop/src-tauri/src/downloads/download_manager.rs +++ b/desktop/src-tauri/src/downloads/download_manager.rs @@ -12,7 +12,8 @@ use log::info; use super::{ download_agent::{GameDownloadAgent, GameDownloadError}, - progress_object::ProgressObject, queue::Queue, + progress_object::ProgressObject, + queue::Queue, }; pub enum DownloadManagerSignal { @@ -20,8 +21,7 @@ pub enum DownloadManagerSignal { Go, /// Pauses the DownloadManager Stop, - /// Called when a GameDownloadAgent has finished. - /// Triggers the next download cycle to begin + /// Called when a GameDownloadAgent has fully completed a download. Completed(String), /// Generates and appends a GameDownloadAgent /// to the registry and queue @@ -104,11 +104,10 @@ impl DownloadManager { ))?; self.command_sender.send(DownloadManagerSignal::Go) } - pub fn cancel_download( - &self, - game_id: String - ) { - self.command_sender.send(DownloadManagerSignal::Cancel(game_id)).unwrap(); + pub fn cancel_download(&self, game_id: String) { + self.command_sender + .send(DownloadManagerSignal::Cancel(game_id)) + .unwrap(); } pub fn edit(&self) -> MutexGuard<'_, VecDeque>> { self.download_queue.edit() @@ -139,11 +138,13 @@ impl DownloadManager { let current_index = get_index_from_id(&mut queue, id).unwrap(); queue.remove(current_index); } - pub fn pause_downloads(&self) -> Result<(), SendError> { - self.command_sender.send(DownloadManagerSignal::Stop) + pub fn pause_downloads(&self) { + self.command_sender + .send(DownloadManagerSignal::Stop) + .unwrap(); } - pub fn resume_downloads(&self) -> Result<(), SendError> { - self.command_sender.send(DownloadManagerSignal::Go) + pub fn resume_downloads(&self) { + self.command_sender.send(DownloadManagerSignal::Go).unwrap(); } pub fn ensure_terminated(self) -> Result, Box> { self.command_sender diff --git a/desktop/src-tauri/src/downloads/download_manager_builder.rs b/desktop/src-tauri/src/downloads/download_manager_builder.rs index e696abf4..656d299d 100644 --- a/desktop/src-tauri/src/downloads/download_manager_builder.rs +++ b/desktop/src-tauri/src/downloads/download_manager_builder.rs @@ -170,15 +170,15 @@ impl DownloadManagerBuilder { fn manage_go_signal(&mut self) { info!("Got signal 'Go'"); - if self.active_control_flag.is_none() && !self.download_agent_registry.is_empty() { + if !self.download_agent_registry.is_empty() && !self.download_queue.empty() { info!("Starting download agent"); - let download_agent = { - let front = self.download_queue.read().front().unwrap().clone(); - self.download_agent_registry.get(&front.id).unwrap().clone() - }; - let download_agent_interface = - Arc::new(AgentInterfaceData::from(download_agent.clone())); - self.current_game_interface = Some(download_agent_interface); + let agent_data = self.download_queue.read().front().unwrap().clone(); + let download_agent = self + .download_agent_registry + .get(&agent_data.id) + .unwrap() + .clone(); + self.current_game_interface = Some(agent_data); let progress_object = download_agent.progress.clone(); *self.progress.lock().unwrap() = Some(progress_object); @@ -191,29 +191,20 @@ impl DownloadManagerBuilder { info!("Spawning download"); spawn(move || { match download_agent.download() { - Ok(_) => { - // TODO wrap this pattern in a macro - let result = sender - .send(DownloadManagerSignal::Completed(download_agent.id.clone())); - if let Err(err) = result { - error!("{}", err); - } - } + // Returns once we've exited the download + // (not necessarily completed) + // The download agent will fire the completed event for us + Ok(_) => {} + // If an error occurred while *starting* the download Err(err) => { - let result = sender.send(DownloadManagerSignal::Error(err)); - if let Err(err) = result { - error!("{}", err); - } + error!("error while managing download: {}", err); + sender.send(DownloadManagerSignal::Error(err)).unwrap(); } }; }); - info!("Finished spawning Download"); active_control_flag.set(DownloadThreadControlFlag::Go); self.set_status(DownloadManagerStatus::Downloading); - } else if let Some(active_control_flag) = self.active_control_flag.clone() { - info!("Restarting current download"); - active_control_flag.set(DownloadThreadControlFlag::Go); } else { info!("Nothing was set"); } @@ -230,6 +221,8 @@ impl DownloadManagerBuilder { self.active_control_flag = None; *self.progress.lock().unwrap() = None; } + // TODO wait until current download exits + self.download_agent_registry.remove(&game_id); let mut lock = self.download_queue.edit(); let index = match lock.iter().position(|interface| interface.id == game_id) { @@ -237,6 +230,8 @@ impl DownloadManagerBuilder { None => return, }; lock.remove(index); + + // Start next download self.sender.send(DownloadManagerSignal::Go).unwrap(); info!( "{:?}", diff --git a/desktop/src-tauri/src/downloads/progress_object.rs b/desktop/src-tauri/src/downloads/progress_object.rs index 0848caf1..9114003f 100644 --- a/desktop/src-tauri/src/downloads/progress_object.rs +++ b/desktop/src-tauri/src/downloads/progress_object.rs @@ -1,13 +1,16 @@ -use std::{sync::{ - atomic::{AtomicUsize, Ordering}, - Arc, Mutex, -}, time::Instant}; +use std::{ + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, Mutex, + }, + time::Instant, +}; #[derive(Clone)] pub struct ProgressObject { max: Arc>, progress_instances: Arc>>>, - start: Arc> + start: Arc>, } impl ProgressObject { diff --git a/desktop/src-tauri/src/downloads/queue.rs b/desktop/src-tauri/src/downloads/queue.rs index 1ce695ff..80e3dc02 100644 --- a/desktop/src-tauri/src/downloads/queue.rs +++ b/desktop/src-tauri/src/downloads/queue.rs @@ -1,19 +1,22 @@ -use std::{collections::VecDeque, sync::{Arc, Mutex, MutexGuard}}; +use std::{ + collections::VecDeque, + sync::{Arc, Mutex, MutexGuard}, +}; use super::download_manager::AgentInterfaceData; #[derive(Clone)] pub struct Queue { - inner: Arc>>> + inner: Arc>>>, } impl Queue { pub fn new() -> Self { Self { - inner: Arc::new(Mutex::new(VecDeque::new())) + inner: Arc::new(Mutex::new(VecDeque::new())), } } - pub fn read(&self) -> VecDeque> { + pub fn read(&self) -> VecDeque> { self.inner.lock().unwrap().clone() } pub fn edit(&self) -> MutexGuard<'_, VecDeque>> { @@ -22,13 +25,15 @@ impl Queue { pub fn pop_front(&self) -> Option> { self.edit().pop_front() } + pub fn empty(&self) -> bool { + self.inner.lock().unwrap().len() == 0 + } /// Either inserts `interface` at the specified index, or appends to /// the back of the deque if index is greater than the length of the deque pub fn insert(&self, interface: AgentInterfaceData, index: usize) { if self.read().len() > index { self.append(interface); - } - else { + } else { self.edit().insert(index, Arc::new(interface)); } } @@ -44,7 +49,7 @@ impl Queue { if front.id == game_id { return queue.pop_front(); } - return None + return None; } pub fn get_by_id(&self, game_id: String) -> Option { self.read().iter().position(|data| data.id == game_id) @@ -61,4 +66,4 @@ impl Queue { self.edit().insert(new_index, existing); Ok(()) } -} \ No newline at end of file +} diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs index 840b8033..8d1dbd2b 100644 --- a/desktop/src-tauri/src/lib.rs +++ b/desktop/src-tauri/src/lib.rs @@ -15,8 +15,8 @@ use db::{ DATA_ROOT_DIR, }; use downloads::download_commands::*; -use downloads::download_manager_builder::DownloadManagerBuilder; use downloads::download_manager::DownloadManager; +use downloads::download_manager_builder::DownloadManagerBuilder; use env_logger::Env; use http::{header::*, response::Builder as ResponseBuilder}; use library::{fetch_game, fetch_game_status, fetch_library, Game}; @@ -137,7 +137,9 @@ pub fn run() { // Downloads download_game, get_current_game_download_progress, - stop_game_download + cancel_game_download, + pause_game_downloads, + resume_game_downloads, ]) .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_dialog::init()) From 9dc052691dcb711cd152862f64e27bcaa4b2a8ad Mon Sep 17 00:00:00 2001 From: DecDuck Date: Thu, 28 Nov 2024 20:31:04 +1100 Subject: [PATCH 21/31] feat(download manager): update db state with ui and emit events --- desktop/app.vue | 3 +- desktop/components/GameStatusButton.vue | 50 ++++++++ desktop/pages/library/[id]/index.vue | 11 +- desktop/src-tauri/src/db.rs | 1 + .../src/downloads/download_manager_builder.rs | 107 +++++++++++------- desktop/src-tauri/src/lib.rs | 14 ++- desktop/src-tauri/src/library.rs | 5 + desktop/{types.d.ts => types.ts} | 1 + 8 files changed, 144 insertions(+), 48 deletions(-) create mode 100644 desktop/components/GameStatusButton.vue rename desktop/{types.d.ts => types.ts} (97%) diff --git a/desktop/app.vue b/desktop/app.vue index 4218fe2d..6ae77a58 100644 --- a/desktop/app.vue +++ b/desktop/app.vue @@ -6,8 +6,7 @@ diff --git a/desktop/pages/library/[id]/index.vue b/desktop/pages/library/[id]/index.vue index 94275bad..6ae50ac8 100644 --- a/desktop/pages/library/[id]/index.vue +++ b/desktop/pages/library/[id]/index.vue @@ -18,7 +18,7 @@
- +
@@ -27,15 +27,22 @@ diff --git a/desktop/src-tauri/src/db.rs b/desktop/src-tauri/src/db.rs index 66d08b5b..b2054059 100644 --- a/desktop/src-tauri/src/db.rs +++ b/desktop/src-tauri/src/db.rs @@ -25,6 +25,7 @@ pub struct DatabaseAuth { #[derive(Serialize, Clone, Deserialize)] pub enum DatabaseGameStatus { Remote, + Queued, Downloading, Installed, Updating, diff --git a/desktop/src-tauri/src/downloads/download_manager_builder.rs b/desktop/src-tauri/src/downloads/download_manager_builder.rs index 656d299d..5c4a723e 100644 --- a/desktop/src-tauri/src/downloads/download_manager_builder.rs +++ b/desktop/src-tauri/src/downloads/download_manager_builder.rs @@ -8,6 +8,9 @@ use std::{ }; use log::{error, info, warn}; +use tauri::{AppHandle, Emitter}; + +use crate::{db::DatabaseGameStatus, library::GameUpdateEvent, DB}; use super::{ download_agent::{GameDownloadAgent, GameDownloadError}, @@ -64,13 +67,14 @@ pub struct DownloadManagerBuilder { sender: Sender, progress: Arc>>, status: Arc>, + app_handle: AppHandle, current_game_interface: Option>, // Should be the only game download agent in the map with the "Go" flag active_control_flag: Option, } impl DownloadManagerBuilder { - pub fn build() -> DownloadManager { + pub fn build(app_handle: AppHandle) -> DownloadManager { let queue = Queue::new(); let (command_sender, command_receiver) = channel(); let active_progress = Arc::new(Mutex::new(None)); @@ -85,6 +89,7 @@ impl DownloadManagerBuilder { status: status.clone(), sender: command_sender.clone(), progress: active_progress.clone(), + app_handle, }; let terminator = spawn(|| manager.manage_queue()); @@ -92,6 +97,23 @@ impl DownloadManagerBuilder { DownloadManager::new(terminator, queue, active_progress, command_sender) } + fn set_game_status(&self, id: String, status: DatabaseGameStatus) { + let mut db_handle = DB.borrow_data_mut().unwrap(); + db_handle + .games + .games_statuses + .insert(id.clone(), status.clone()); + self.app_handle + .emit( + &format!("update_game/{}", id), + GameUpdateEvent { + game_id: id, + status: status, + }, + ) + .unwrap(); + } + fn manage_queue(mut self) -> Result<(), ()> { loop { let signal = match self.command_receiver.recv() { @@ -145,6 +167,8 @@ impl DownloadManagerBuilder { self.download_agent_registry.remove(&game_id); self.active_control_flag = None; *self.progress.lock().unwrap() = None; + + self.set_game_status(game_id, DatabaseGameStatus::Installed); } } self.sender.send(DownloadManagerSignal::Go).unwrap(); @@ -160,54 +184,61 @@ impl DownloadManagerBuilder { )); let agent_status = GameDownloadStatus::Uninitialised; let interface_data = AgentInterfaceData { - id, + id: id.clone(), status: Mutex::new(agent_status), }; self.download_agent_registry .insert(interface_data.id.clone(), download_agent); self.download_queue.append(interface_data); + + self.set_game_status(id, DatabaseGameStatus::Queued); } fn manage_go_signal(&mut self) { info!("Got signal 'Go'"); - if !self.download_agent_registry.is_empty() && !self.download_queue.empty() { - info!("Starting download agent"); - let agent_data = self.download_queue.read().front().unwrap().clone(); - let download_agent = self - .download_agent_registry - .get(&agent_data.id) - .unwrap() - .clone(); - self.current_game_interface = Some(agent_data); - let progress_object = download_agent.progress.clone(); - *self.progress.lock().unwrap() = Some(progress_object); - - let active_control_flag = download_agent.control_flag.clone(); - self.active_control_flag = Some(active_control_flag.clone()); - - let sender = self.sender.clone(); - - info!("Spawning download"); - spawn(move || { - match download_agent.download() { - // Returns once we've exited the download - // (not necessarily completed) - // The download agent will fire the completed event for us - Ok(_) => {} - // If an error occurred while *starting* the download - Err(err) => { - error!("error while managing download: {}", err); - sender.send(DownloadManagerSignal::Error(err)).unwrap(); - } - }; - }); - - active_control_flag.set(DownloadThreadControlFlag::Go); - self.set_status(DownloadManagerStatus::Downloading); - } else { - info!("Nothing was set"); + if !(!self.download_agent_registry.is_empty() && !self.download_queue.empty()) { + return; } + + info!("Starting download agent"); + let agent_data = self.download_queue.read().front().unwrap().clone(); + let download_agent = self + .download_agent_registry + .get(&agent_data.id) + .unwrap() + .clone(); + self.current_game_interface = Some(agent_data); + + let progress_object = download_agent.progress.clone(); + *self.progress.lock().unwrap() = Some(progress_object); + + let active_control_flag = download_agent.control_flag.clone(); + self.active_control_flag = Some(active_control_flag.clone()); + + let sender = self.sender.clone(); + + info!("Spawning download"); + spawn(move || { + match download_agent.download() { + // Returns once we've exited the download + // (not necessarily completed) + // The download agent will fire the completed event for us + Ok(_) => {} + // If an error occurred while *starting* the download + Err(err) => { + error!("error while managing download: {}", err); + sender.send(DownloadManagerSignal::Error(err)).unwrap(); + } + }; + }); + + active_control_flag.set(DownloadThreadControlFlag::Go); + self.set_status(DownloadManagerStatus::Downloading); + self.set_game_status( + self.current_game_interface.as_ref().unwrap().id.clone(), + DatabaseGameStatus::Downloading, + ); } fn manage_error_signal(&self, error: GameDownloadError) { let current_status = self.current_game_interface.clone().unwrap(); diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs index 8d1dbd2b..a245d3dd 100644 --- a/desktop/src-tauri/src/lib.rs +++ b/desktop/src-tauri/src/lib.rs @@ -28,6 +28,7 @@ use std::{ collections::HashMap, sync::{LazyLock, Mutex}, }; +use tauri::{AppHandle, Manager}; use tauri_plugin_deep_link::DeepLinkExt; #[derive(Clone, Copy, Serialize)] @@ -69,12 +70,12 @@ fn fetch_state(state: tauri::State<'_, Mutex>) -> Result AppState { +fn setup(handle: AppHandle) -> AppState { debug!("Starting env"); env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); let games = HashMap::new(); - let download_manager = Arc::new(DownloadManagerBuilder::build()); + let download_manager = Arc::new(DownloadManagerBuilder::build(handle)); debug!("Checking if database is set up"); let is_set_up = DB.database_is_set_up(); @@ -102,9 +103,6 @@ pub static DB: LazyLock = LazyLock::new(DatabaseInterface::se #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { - let state = setup(); - info!("initialized drop client"); - let mut builder = tauri::Builder::default().plugin(tauri_plugin_dialog::init()); #[cfg(desktop)] @@ -117,7 +115,6 @@ pub fn run() { builder .plugin(tauri_plugin_deep_link::init()) - .manage(Mutex::new(state)) .invoke_handler(tauri::generate_handler![ // DB fetch_state, @@ -144,6 +141,11 @@ pub fn run() { .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_dialog::init()) .setup(|app| { + let handle = app.handle().clone(); + let state = setup(handle); + info!("initialized drop client"); + app.manage(Mutex::new(state)); + #[cfg(any(target_os = "linux", all(debug_assertions, windows)))] { use tauri_plugin_deep_link::DeepLinkExt; diff --git a/desktop/src-tauri/src/library.rs b/desktop/src-tauri/src/library.rs index 64372b8c..fe792a30 100644 --- a/desktop/src-tauri/src/library.rs +++ b/desktop/src-tauri/src/library.rs @@ -33,6 +33,11 @@ pub struct Game { m_cover_id: String, m_image_library: Vec, } +#[derive(serde::Serialize, Clone)] +pub struct GameUpdateEvent { + pub game_id: String, + pub status: DatabaseGameStatus, +} fn fetch_library_logic(app: AppHandle) -> Result { let base_url = DB.fetch_base_url(); diff --git a/desktop/types.d.ts b/desktop/types.ts similarity index 97% rename from desktop/types.d.ts rename to desktop/types.ts index b63d9043..9b91f485 100644 --- a/desktop/types.d.ts +++ b/desktop/types.ts @@ -27,6 +27,7 @@ export enum AppStatus { export enum GameStatus { Remote = "Remote", + Queued = "Queued", Downloading = "Downloading", Installed = "Installed", Updating = "Updating", From e6553e6ed1814f24c393696a0f4b89c00553af4b Mon Sep 17 00:00:00 2001 From: DecDuck Date: Wed, 4 Dec 2024 17:29:46 +1100 Subject: [PATCH 22/31] feat(download manager): syncs state to disk to persist across reboots --- desktop/src-tauri/src/downloads/download_manager_builder.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/desktop/src-tauri/src/downloads/download_manager_builder.rs b/desktop/src-tauri/src/downloads/download_manager_builder.rs index 5c4a723e..822549c1 100644 --- a/desktop/src-tauri/src/downloads/download_manager_builder.rs +++ b/desktop/src-tauri/src/downloads/download_manager_builder.rs @@ -103,6 +103,8 @@ impl DownloadManagerBuilder { .games .games_statuses .insert(id.clone(), status.clone()); + drop(db_handle); + DB.save().unwrap(); self.app_handle .emit( &format!("update_game/{}", id), From 3d55cb768ac1ca71115105dbd15d70bf70a3af9e Mon Sep 17 00:00:00 2001 From: DecDuck Date: Fri, 6 Dec 2024 22:16:50 +1100 Subject: [PATCH 23/31] feat(install ui): ui to install games --- desktop/components/GameStatusButton.vue | 47 ++- desktop/components/LoadingButton.vue | 2 +- desktop/pages/library/[id]/index.vue | 332 +++++++++++++++++- desktop/pages/store/index.vue | 5 - .../src-tauri/src/downloads/download_agent.rs | 3 +- .../src/downloads/download_commands.rs | 3 +- .../src-tauri/src/downloads/download_logic.rs | 17 +- .../src/downloads/download_manager_builder.rs | 6 + desktop/src-tauri/src/downloads/manifest.rs | 1 + desktop/src-tauri/src/lib.rs | 3 +- desktop/src-tauri/src/library.rs | 42 +++ 11 files changed, 436 insertions(+), 25 deletions(-) diff --git a/desktop/components/GameStatusButton.vue b/desktop/components/GameStatusButton.vue index e23c314a..a7214425 100644 --- a/desktop/components/GameStatusButton.vue +++ b/desktop/components/GameStatusButton.vue @@ -1,29 +1,45 @@ diff --git a/desktop/components/LoadingButton.vue b/desktop/components/LoadingButton.vue index b61303f4..ec0adfac 100644 --- a/desktop/components/LoadingButton.vue +++ b/desktop/components/LoadingButton.vue @@ -1,7 +1,7 @@ diff --git a/desktop/pages/store/index.vue b/desktop/pages/store/index.vue index 85d220b4..696d67cd 100644 --- a/desktop/pages/store/index.vue +++ b/desktop/pages/store/index.vue @@ -39,11 +39,6 @@ const versionName = ref(""); const progress = ref(0); async function startGameDownload() { - await invoke("download_game", { - gameId: gameId.value, - gameVersion: versionName.value, - }); - setInterval(() => { (async () => { const currentProgress = await invoke( diff --git a/desktop/src-tauri/src/downloads/download_agent.rs b/desktop/src-tauri/src/downloads/download_agent.rs index 2e960e34..e6c630b9 100644 --- a/desktop/src-tauri/src/downloads/download_agent.rs +++ b/desktop/src-tauri/src/downloads/download_agent.rs @@ -172,7 +172,6 @@ impl GameDownloadAgent { pub fn ensure_contexts(&self) -> Result<(), GameDownloadError> { let context_lock = self.contexts.lock().unwrap(); - info!("{:?} {}", context_lock, context_lock.is_empty()); if !context_lock.is_empty() { return Ok(()); } @@ -209,7 +208,7 @@ impl GameDownloadAgent { for (i, length) in chunk.lengths.iter().enumerate() { contexts.push(DropDownloadContext { file_name: raw_path.to_string(), - version: version.to_string(), + version: chunk.versionName.to_string(), offset: running_offset, index: i, game_id: game_id.to_string(), diff --git a/desktop/src-tauri/src/downloads/download_commands.rs b/desktop/src-tauri/src/downloads/download_commands.rs index c6f3c426..044a0677 100644 --- a/desktop/src-tauri/src/downloads/download_commands.rs +++ b/desktop/src-tauri/src/downloads/download_commands.rs @@ -8,13 +8,14 @@ use crate::AppState; pub fn download_game( game_id: String, game_version: String, + install_dir: usize, state: tauri::State<'_, Mutex>, ) -> Result<(), String> { state .lock() .unwrap() .download_manager - .queue_game(game_id, game_version, 0) + .queue_game(game_id, game_version, install_dir) .map_err(|_| "An error occurred while communicating with the download manager.".to_string()) } diff --git a/desktop/src-tauri/src/downloads/download_logic.rs b/desktop/src-tauri/src/downloads/download_logic.rs index ed2645f6..a290970d 100644 --- a/desktop/src-tauri/src/downloads/download_logic.rs +++ b/desktop/src-tauri/src/downloads/download_logic.rs @@ -3,7 +3,7 @@ use crate::db::DatabaseImpls; use crate::downloads::manifest::DropDownloadContext; use crate::remote::RemoteAccessError; use crate::DB; -use log::info; +use log::{info, warn}; use md5::{Context, Digest}; use reqwest::blocking::Response; @@ -150,6 +150,13 @@ pub fn download_game_chunk( .send() .map_err(|e| GameDownloadError::Communication(e.into()))?; + if response.status() != 200 { + warn!("{}", response.text().unwrap()); + return Err(GameDownloadError::Communication( + RemoteAccessError::InvalidCodeError(400), + )); + } + let mut destination = DropWriter::new(ctx.path); if ctx.offset != 0 { @@ -160,7 +167,9 @@ pub fn download_game_chunk( let content_length = response.content_length(); if content_length.is_none() { - return Err(GameDownloadError::Communication(RemoteAccessError::InvalidResponse)); + return Err(GameDownloadError::Communication( + RemoteAccessError::InvalidResponse, + )); } let mut pipeline = DropDownloadPipeline::new( @@ -176,7 +185,9 @@ pub fn download_game_chunk( return Ok(false); }; - let checksum = pipeline.finish().map_err(|e| GameDownloadError::IoError(e))?; + let checksum = pipeline + .finish() + .map_err(|e| GameDownloadError::IoError(e))?; let res = hex::encode(checksum.0); if res != ctx.checksum { diff --git a/desktop/src-tauri/src/downloads/download_manager_builder.rs b/desktop/src-tauri/src/downloads/download_manager_builder.rs index 822549c1..e9990dbe 100644 --- a/desktop/src-tauri/src/downloads/download_manager_builder.rs +++ b/desktop/src-tauri/src/downloads/download_manager_builder.rs @@ -8,6 +8,7 @@ use std::{ }; use log::{error, info, warn}; +use rustbreak::Database; use tauri::{AppHandle, Emitter}; use crate::{db::DatabaseGameStatus, library::GameUpdateEvent, DB}; @@ -247,6 +248,11 @@ impl DownloadManagerBuilder { let mut lock = current_status.status.lock().unwrap(); *lock = GameDownloadStatus::Error; self.set_status(DownloadManagerStatus::Error(error)); + + self.set_game_status( + self.current_game_interface.as_ref().unwrap().id.clone(), + DatabaseGameStatus::Remote, + ); } fn manage_cancel_signal(&mut self, game_id: String) { if let Some(current_flag) = &self.active_control_flag { diff --git a/desktop/src-tauri/src/downloads/manifest.rs b/desktop/src-tauri/src/downloads/manifest.rs index 2c28ea5a..d6cf8ec8 100644 --- a/desktop/src-tauri/src/downloads/manifest.rs +++ b/desktop/src-tauri/src/downloads/manifest.rs @@ -9,6 +9,7 @@ pub struct DropChunk { pub ids: Vec, pub checksums: Vec, pub lengths: Vec, + pub versionName: String, } #[derive(Serialize, Deserialize, Debug, Clone)] diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs index a245d3dd..0d9a505c 100644 --- a/desktop/src-tauri/src/lib.rs +++ b/desktop/src-tauri/src/lib.rs @@ -19,7 +19,7 @@ use downloads::download_manager::DownloadManager; use downloads::download_manager_builder::DownloadManagerBuilder; use env_logger::Env; use http::{header::*, response::Builder as ResponseBuilder}; -use library::{fetch_game, fetch_game_status, fetch_library, Game}; +use library::{fetch_game, fetch_game_status, fetch_game_verion_options, fetch_library, Game}; use log::{debug, info}; use remote::{gen_drop_url, use_remote, RemoteAccessError}; use serde::{Deserialize, Serialize}; @@ -131,6 +131,7 @@ pub fn run() { delete_download_dir, fetch_download_dir_stats, fetch_game_status, + fetch_game_verion_options, // Downloads download_game, get_current_game_download_progress, diff --git a/desktop/src-tauri/src/library.rs b/desktop/src-tauri/src/library.rs index fe792a30..0afb0bc9 100644 --- a/desktop/src-tauri/src/library.rs +++ b/desktop/src-tauri/src/library.rs @@ -39,6 +39,19 @@ pub struct GameUpdateEvent { pub status: DatabaseGameStatus, } +// Game version with some fields missing and size information +#[derive(serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GameVersionOption { + version_index: usize, + version_name: String, + platform: String, + setup_command: String, + launch_command: String, + delta: bool, + // total_size: usize, +} + fn fetch_library_logic(app: AppHandle) -> Result { let base_url = DB.fetch_base_url(); let library_url = base_url.join("/api/v1/client/user/library")?; @@ -172,3 +185,32 @@ pub fn fetch_game_status(id: String) -> Result { return Ok(status); } + +fn fetch_game_verion_options_logic(game_id: String) -> Result, RemoteAccessError> { + let base_url = DB.fetch_base_url(); + + let endpoint = + base_url.join(format!("/api/v1/client/metadata/versions?id={}", game_id).as_str())?; + let header = generate_authorization_header(); + + let client = reqwest::blocking::Client::new(); + let response = client + .get(endpoint.to_string()) + .header("Authorization", header) + .send()?; + + if response.status() != 200 { + return Err(RemoteAccessError::InvalidCodeError( + response.status().into(), + )); + } + + let data = response.json::>()?; + + return Ok(data); +} + +#[tauri::command] +pub fn fetch_game_verion_options(game_id: String) -> Result, String> { + fetch_game_verion_options_logic(game_id).map_err(|e| e.to_string()) +} From 2e5997525d0786eeb5b382aba6a75a881df1d8a2 Mon Sep 17 00:00:00 2001 From: DecDuck Date: Sat, 7 Dec 2024 11:00:35 +1100 Subject: [PATCH 24/31] feat(download & db): combined db and download interface improvements --- desktop/components/GameStatusButton.vue | 72 +++++++++-------- desktop/pages/library/[id]/index.vue | 6 +- desktop/src-tauri/Cargo.lock | 17 ---- desktop/src-tauri/Cargo.toml | 12 +-- desktop/src-tauri/src/db.rs | 50 +++++++++--- .../src-tauri/src/downloads/download_agent.rs | 1 - .../src/downloads/download_manager_builder.rs | 19 +++-- desktop/src-tauri/src/lib.rs | 2 + desktop/src-tauri/src/library.rs | 79 +++++++++++++++++-- desktop/types.ts | 8 +- 10 files changed, 186 insertions(+), 80 deletions(-) diff --git a/desktop/components/GameStatusButton.vue b/desktop/components/GameStatusButton.vue index a7214425..972de001 100644 --- a/desktop/components/GameStatusButton.vue +++ b/desktop/components/GameStatusButton.vue @@ -1,18 +1,18 @@ @@ -22,9 +22,10 @@ import { PlayIcon, QueueListIcon, TrashIcon, + WrenchIcon, } from "@heroicons/vue/20/solid"; import type { Component } from "vue"; -import { GameStatus } from "~/types.js"; +import { GameStatusEnum, type GameStatus } from "~/types.js"; const props = defineProps<{ status: GameStatus }>(); const emit = defineEmits<{ @@ -33,43 +34,48 @@ const emit = defineEmits<{ (e: "play"): void; }>(); -const styles: { [key in GameStatus]: string } = { - [GameStatus.Remote]: +const styles: { [key in GameStatusEnum]: string } = { + [GameStatusEnum.Remote]: "bg-blue-600 text-white hover:bg-blue-500 focus-visible:outline-blue-600", - [GameStatus.Queued]: + [GameStatusEnum.Queued]: "bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700", - [GameStatus.Downloading]: + [GameStatusEnum.Downloading]: "bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700", - [GameStatus.Installed]: + [GameStatusEnum.SetupRequired]: + "bg-yellow-600 text-white hover:bg-yellow-500 focus-visible:outline-yellow-600", + [GameStatusEnum.Installed]: "bg-green-600 text-white hover:bg-green-500 focus-visible:outline-green-600", - [GameStatus.Updating]: "", - [GameStatus.Uninstalling]: "", + [GameStatusEnum.Updating]: "", + [GameStatusEnum.Uninstalling]: "", }; -const buttonNames: { [key in GameStatus]: string } = { - [GameStatus.Remote]: "Install", - [GameStatus.Queued]: "Queued", - [GameStatus.Downloading]: "Downloading", - [GameStatus.Installed]: "Play", - [GameStatus.Updating]: "Updating", - [GameStatus.Uninstalling]: "Uninstalling", +const buttonNames: { [key in GameStatusEnum]: string } = { + [GameStatusEnum.Remote]: "Install", + [GameStatusEnum.Queued]: "Queued", + [GameStatusEnum.Downloading]: "Downloading", + [GameStatusEnum.SetupRequired]: "Setup", + [GameStatusEnum.Installed]: "Play", + [GameStatusEnum.Updating]: "Updating", + [GameStatusEnum.Uninstalling]: "Uninstalling", }; -const buttonIcons: { [key in GameStatus]: Component } = { - [GameStatus.Remote]: ArrowDownTrayIcon, - [GameStatus.Queued]: QueueListIcon, - [GameStatus.Downloading]: ArrowDownTrayIcon, - [GameStatus.Installed]: PlayIcon, - [GameStatus.Updating]: ArrowDownTrayIcon, - [GameStatus.Uninstalling]: TrashIcon, +const buttonIcons: { [key in GameStatusEnum]: Component } = { + [GameStatusEnum.Remote]: ArrowDownTrayIcon, + [GameStatusEnum.Queued]: QueueListIcon, + [GameStatusEnum.Downloading]: ArrowDownTrayIcon, + [GameStatusEnum.SetupRequired]: WrenchIcon, + [GameStatusEnum.Installed]: PlayIcon, + [GameStatusEnum.Updating]: ArrowDownTrayIcon, + [GameStatusEnum.Uninstalling]: TrashIcon, }; -const buttonActions: { [key in GameStatus]: () => void } = { - [GameStatus.Remote]: () => emit("install"), - [GameStatus.Queued]: () => emit("cancel"), - [GameStatus.Downloading]: () => emit("cancel"), - [GameStatus.Installed]: () => emit("play"), - [GameStatus.Updating]: () => emit("cancel"), - [GameStatus.Uninstalling]: () => {}, +const buttonActions: { [key in GameStatusEnum]: () => void } = { + [GameStatusEnum.Remote]: () => emit("install"), + [GameStatusEnum.Queued]: () => emit("cancel"), + [GameStatusEnum.Downloading]: () => emit("cancel"), + [GameStatusEnum.SetupRequired]: () => {}, + [GameStatusEnum.Installed]: () => emit("play"), + [GameStatusEnum.Updating]: () => emit("cancel"), + [GameStatusEnum.Uninstalling]: () => {}, }; diff --git a/desktop/pages/library/[id]/index.vue b/desktop/pages/library/[id]/index.vue index b930d761..9d914ab2 100644 --- a/desktop/pages/library/[id]/index.vue +++ b/desktop/pages/library/[id]/index.vue @@ -275,7 +275,11 @@ > , // Guaranteed to exist if the game also exists in the app state map pub games_statuses: HashMap, + pub game_versions: HashMap>, } #[derive(Serialize, Clone, Deserialize)] @@ -50,8 +65,22 @@ pub struct Database { pub static DATA_ROOT_DIR: LazyLock> = LazyLock::new(|| Mutex::new(BaseDirs::new().unwrap().data_dir().join("drop"))); +// Custom JSON serializer to support everything we need +#[derive(Debug, Default, Clone)] +pub struct DropDatabaseSerializer; + +impl DeSerializer for DropDatabaseSerializer { + fn serialize(&self, val: &T) -> rustbreak::error::DeSerResult> { + serde_json::to_vec(val).map_err(|e| DeSerError::Internal(e.to_string())) + } + + fn deserialize(&self, s: R) -> rustbreak::error::DeSerResult { + serde_json::from_reader(s).map_err(|e| DeSerError::Internal(e.to_string())) + } +} + pub type DatabaseInterface = - rustbreak::Database; + rustbreak::Database; pub trait DatabaseImpls { fn set_up_database() -> DatabaseInterface; @@ -80,6 +109,7 @@ impl DatabaseImpls for DatabaseInterface { games: DatabaseGames { install_dirs: vec![games_base_dir.to_str().unwrap().to_string()], games_statuses: HashMap::new(), + game_versions: HashMap::new(), }, }; debug!("Creating database at path {}", db_path.as_str().unwrap()); diff --git a/desktop/src-tauri/src/downloads/download_agent.rs b/desktop/src-tauri/src/downloads/download_agent.rs index e6c630b9..2b3c26ac 100644 --- a/desktop/src-tauri/src/downloads/download_agent.rs +++ b/desktop/src-tauri/src/downloads/download_agent.rs @@ -187,7 +187,6 @@ impl GameDownloadAgent { drop(db_lock); let manifest = self.manifest.lock().unwrap().clone().unwrap(); - let version = self.version.clone(); let game_id = self.id.clone(); let data_base_dir_path = Path::new(&data_base_dir); diff --git a/desktop/src-tauri/src/downloads/download_manager_builder.rs b/desktop/src-tauri/src/downloads/download_manager_builder.rs index e9990dbe..a1578a50 100644 --- a/desktop/src-tauri/src/downloads/download_manager_builder.rs +++ b/desktop/src-tauri/src/downloads/download_manager_builder.rs @@ -11,7 +11,11 @@ use log::{error, info, warn}; use rustbreak::Database; use tauri::{AppHandle, Emitter}; -use crate::{db::DatabaseGameStatus, library::GameUpdateEvent, DB}; +use crate::{ + db::DatabaseGameStatus, + library::{on_game_complete, GameUpdateEvent}, + DB, +}; use super::{ download_agent::{GameDownloadAgent, GameDownloadError}, @@ -167,11 +171,11 @@ impl DownloadManagerBuilder { if interface.id == game_id { info!("Popping consumed data"); self.download_queue.pop_front(); - self.download_agent_registry.remove(&game_id); + let download_agent = self.download_agent_registry.remove(&game_id).unwrap(); self.active_control_flag = None; *self.progress.lock().unwrap() = None; - self.set_game_status(game_id, DatabaseGameStatus::Installed); + on_game_complete(game_id, download_agent.version.clone(), &self.app_handle); } } self.sender.send(DownloadManagerSignal::Go).unwrap(); @@ -190,11 +194,12 @@ impl DownloadManagerBuilder { id: id.clone(), status: Mutex::new(agent_status), }; + let version_name = download_agent.version.clone(); self.download_agent_registry .insert(interface_data.id.clone(), download_agent); self.download_queue.append(interface_data); - self.set_game_status(id, DatabaseGameStatus::Queued); + self.set_game_status(id, DatabaseGameStatus::Queued { version_name }); } fn manage_go_signal(&mut self) { @@ -213,6 +218,8 @@ impl DownloadManagerBuilder { .clone(); self.current_game_interface = Some(agent_data); + let version_name = download_agent.version.clone(); + let progress_object = download_agent.progress.clone(); *self.progress.lock().unwrap() = Some(progress_object); @@ -240,7 +247,7 @@ impl DownloadManagerBuilder { self.set_status(DownloadManagerStatus::Downloading); self.set_game_status( self.current_game_interface.as_ref().unwrap().id.clone(), - DatabaseGameStatus::Downloading, + DatabaseGameStatus::Downloading { version_name }, ); } fn manage_error_signal(&self, error: GameDownloadError) { @@ -251,7 +258,7 @@ impl DownloadManagerBuilder { self.set_game_status( self.current_game_interface.as_ref().unwrap().id.clone(), - DatabaseGameStatus::Remote, + DatabaseGameStatus::Remote {}, ); } fn manage_cancel_signal(&mut self, game_id: String) { diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs index 0d9a505c..ef307880 100644 --- a/desktop/src-tauri/src/lib.rs +++ b/desktop/src-tauri/src/lib.rs @@ -1,3 +1,5 @@ +#![feature(map_try_insert)] + mod auth; mod db; mod downloads; diff --git a/desktop/src-tauri/src/library.rs b/desktop/src-tauri/src/library.rs index 0afb0bc9..041cf2ef 100644 --- a/desktop/src-tauri/src/library.rs +++ b/desktop/src-tauri/src/library.rs @@ -1,14 +1,17 @@ +use std::collections::HashMap; use std::fmt::format; use std::sync::Mutex; use log::info; use serde::{Deserialize, Serialize}; use serde_json::json; +use tauri::Emitter; use tauri::{AppHandle, Manager}; +use urlencoding::encode; -use crate::db; use crate::db::DatabaseGameStatus; use crate::db::DatabaseImpls; +use crate::db::{self, GameVersion}; use crate::downloads::download_manager::GameDownloadStatus; use crate::remote::RemoteAccessError; use crate::{auth::generate_authorization_header, AppState, DB}; @@ -68,7 +71,7 @@ fn fetch_library_logic(app: AppHandle) -> Result { return Err(response.status().as_u16().into()); } - let games = response.json::>()?; + let games: Vec = response.json::>()?; let state = app.state::>(); let mut handle = state.lock().unwrap(); @@ -81,7 +84,7 @@ fn fetch_library_logic(app: AppHandle) -> Result { db_handle .games .games_statuses - .insert(game.id.clone(), DatabaseGameStatus::Remote); + .insert(game.id.clone(), DatabaseGameStatus::Remote {}); } } @@ -145,7 +148,7 @@ fn fetch_game_logic(id: String, app: tauri::AppHandle) -> Result Result { .games .games_statuses .get(&id) - .unwrap_or(&DatabaseGameStatus::Remote) + .unwrap_or(&DatabaseGameStatus::Remote {}) .clone(); drop(db_handle); return Ok(status); } -fn fetch_game_verion_options_logic(game_id: String) -> Result, RemoteAccessError> { +fn fetch_game_verion_options_logic( + game_id: String, +) -> Result, RemoteAccessError> { let base_url = DB.fetch_base_url(); let endpoint = @@ -214,3 +219,65 @@ fn fetch_game_verion_options_logic(game_id: String) -> Result Result, String> { fetch_game_verion_options_logic(game_id).map_err(|e| e.to_string()) } + +pub fn on_game_complete( + game_id: String, + version_name: String, + app_handle: &AppHandle, +) -> Result<(), RemoteAccessError> { + // Fetch game version information from remote + let base_url = DB.fetch_base_url(); + + let endpoint = base_url.join( + format!( + "/api/v1/client/metadata/version?id={}&version={}", + game_id, + encode(&version_name) + ) + .as_str(), + )?; + let header = generate_authorization_header(); + + let client = reqwest::blocking::Client::new(); + let response = client + .get(endpoint.to_string()) + .header("Authorization", header) + .send()?; + + let data = response.json::()?; + + let mut handle = DB.borrow_data_mut().unwrap(); + handle + .games + .game_versions + .entry(game_id.clone()) + .or_insert(HashMap::new()) + .insert(version_name.clone(), data.clone()); + drop(handle); + DB.save().unwrap(); + + let status = if data.setup_command.is_empty() { + DatabaseGameStatus::Installed { version_name } + } else { + DatabaseGameStatus::SetupRequired { version_name } + }; + + let mut db_handle = DB.borrow_data_mut().unwrap(); + db_handle + .games + .games_statuses + .insert(game_id.clone(), status.clone()); + drop(db_handle); + DB.save().unwrap(); + app_handle + .emit( + &format!("update_game/{}", game_id), + GameUpdateEvent { + game_id: game_id, + status: status, + }, + ) + .unwrap(); + + Ok(()) +} diff --git a/desktop/types.ts b/desktop/types.ts index 9b91f485..3a6e3f46 100644 --- a/desktop/types.ts +++ b/desktop/types.ts @@ -25,11 +25,17 @@ export enum AppStatus { ServerUnavailable = "ServerUnavailable", } -export enum GameStatus { +export enum GameStatusEnum { Remote = "Remote", Queued = "Queued", Downloading = "Downloading", Installed = "Installed", Updating = "Updating", Uninstalling = "Uninstalling", + SetupRequired = "SetupRequired", } + +export type GameStatus = { + type: GameStatusEnum; + version_name?: string; +}; From 57fe5eae054932341218593246fa8519e2ceda0f Mon Sep 17 00:00:00 2001 From: DecDuck Date: Sat, 7 Dec 2024 20:21:22 +1100 Subject: [PATCH 25/31] feat: temporary queue ui and flamegraph instructions --- desktop/.gitignore | 5 +- desktop/DEBUG.md | 15 +++++ desktop/app.vue | 2 + desktop/composables/queue.ts | 13 ++++ desktop/layouts/default.vue | 6 ++ .../src-tauri/src/downloads/download_agent.rs | 10 +-- .../src-tauri/src/downloads/download_logic.rs | 12 ++-- .../src/downloads/download_manager.rs | 26 +++++--- .../src/downloads/download_manager_builder.rs | 60 ++++++++++++++--- .../src/downloads/progress_object.rs | 64 ++++++++++++++++++- desktop/src-tauri/src/downloads/queue.rs | 16 ++--- desktop/src-tauri/src/library.rs | 14 +++- 12 files changed, 201 insertions(+), 42 deletions(-) create mode 100644 desktop/DEBUG.md create mode 100644 desktop/composables/queue.ts diff --git a/desktop/.gitignore b/desktop/.gitignore index 2f4d8228..8e8efd66 100644 --- a/desktop/.gitignore +++ b/desktop/.gitignore @@ -23,4 +23,7 @@ dist-ssr *.sln *.sw? .nuxt -.output \ No newline at end of file +.output + +src-tauri/flamegraph.svg +src-tuair/perf* \ No newline at end of file diff --git a/desktop/DEBUG.md b/desktop/DEBUG.md new file mode 100644 index 00000000..2a4478f3 --- /dev/null +++ b/desktop/DEBUG.md @@ -0,0 +1,15 @@ +# How to create Flamegraph + +Run this in `src-tauri`: +``` +WEBKIT_DISABLE_DMABUF_RENDERER=1 CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph --release +``` + +You can leave out `WEBKIT_DISABLE_DMABUF_RENDERER=1` if you're not on NVIDIA/Linux + +And then run this in the root dir: +``` +yarn dev --port 1432 +``` + +And then do what you want, and it'll create the flamegraph for you diff --git a/desktop/app.vue b/desktop/app.vue index 6ae77a58..a5c65842 100644 --- a/desktop/app.vue +++ b/desktop/app.vue @@ -5,6 +5,8 @@ diff --git a/desktop/src-tauri/src/downloads/download_agent.rs b/desktop/src-tauri/src/downloads/download_agent.rs index 2b3c26ac..0b429d12 100644 --- a/desktop/src-tauri/src/downloads/download_agent.rs +++ b/desktop/src-tauri/src/downloads/download_agent.rs @@ -1,6 +1,7 @@ use crate::auth::generate_authorization_header; use crate::db::DatabaseImpls; use crate::downloads::manifest::{DropDownloadContext, DropManifest}; +use crate::downloads::progress_object::ProgressHandle; use crate::remote::RemoteAccessError; use crate::DB; use log::{debug, error, info}; @@ -28,7 +29,7 @@ pub struct GameDownloadAgent { pub target_download_dir: usize, contexts: Mutex>, pub manifest: Mutex>, - pub progress: ProgressObject, + pub progress: Arc, sender: Sender, } @@ -76,7 +77,7 @@ impl GameDownloadAgent { manifest: Mutex::new(None), target_download_dir, contexts: Mutex::new(Vec::new()), - progress: ProgressObject::new(0, 0), + progress: Arc::new(ProgressObject::new(0, 0, sender.clone())), sender, } } @@ -234,7 +235,7 @@ impl GameDownloadAgent { pub fn run(&self) -> Result<(), ()> { info!("downloading game: {}", self.id); - const DOWNLOAD_MAX_THREADS: usize = 4; + const DOWNLOAD_MAX_THREADS: usize = 1; let pool = ThreadPoolBuilder::new() .num_threads(DOWNLOAD_MAX_THREADS) @@ -251,10 +252,11 @@ impl GameDownloadAgent { let context = context.clone(); let control_flag = self.control_flag.clone(); // Clone arcs let progress = self.progress.get(index); // Clone arcs + let progress_handle = ProgressHandle::new(progress, self.progress.clone()); let completed_indexes_ref = completed_indexes_loop_arc.clone(); scope.spawn(move |_| { - match download_game_chunk(context.clone(), control_flag, progress) { + match download_game_chunk(context.clone(), control_flag, progress_handle) { Ok(res) => match res { true => { let mut lock = completed_indexes_ref.lock().unwrap(); diff --git a/desktop/src-tauri/src/downloads/download_logic.rs b/desktop/src-tauri/src/downloads/download_logic.rs index a290970d..ff0bba6a 100644 --- a/desktop/src-tauri/src/downloads/download_logic.rs +++ b/desktop/src-tauri/src/downloads/download_logic.rs @@ -19,6 +19,7 @@ use urlencoding::encode; use super::download_agent::GameDownloadError; use super::download_thread_control_flag::{DownloadThreadControl, DownloadThreadControlFlag}; +use super::progress_object::{ProgressHandle, ProgressObject}; pub struct DropWriter { hasher: Context, @@ -65,7 +66,7 @@ pub struct DropDownloadPipeline { pub source: R, pub destination: DropWriter, pub control_flag: DownloadThreadControl, - pub progress: Arc, + pub progress: ProgressHandle, pub size: usize, } impl DropDownloadPipeline { @@ -73,7 +74,7 @@ impl DropDownloadPipeline { source: Response, destination: DropWriter, control_flag: DownloadThreadControl, - progress: Arc, + progress: ProgressHandle, size: usize, ) -> Self { Self { @@ -100,8 +101,7 @@ impl DropDownloadPipeline { current_size += bytes_read; buf_writer.write_all(©_buf[0..bytes_read])?; - self.progress - .fetch_add(bytes_read, std::sync::atomic::Ordering::Relaxed); + self.progress.add(bytes_read); if current_size == self.size { break; @@ -120,11 +120,11 @@ impl DropDownloadPipeline { pub fn download_game_chunk( ctx: DropDownloadContext, control_flag: DownloadThreadControl, - progress: Arc, + progress: ProgressHandle, ) -> Result { // If we're paused if control_flag.get() == DownloadThreadControlFlag::Stop { - progress.store(0, Ordering::Relaxed); + progress.set(0); return Ok(false); } diff --git a/desktop/src-tauri/src/downloads/download_manager.rs b/desktop/src-tauri/src/downloads/download_manager.rs index f57c87c2..e70d53db 100644 --- a/desktop/src-tauri/src/downloads/download_manager.rs +++ b/desktop/src-tauri/src/downloads/download_manager.rs @@ -9,9 +9,11 @@ use std::{ }; use log::info; +use serde::Serialize; use super::{ download_agent::{GameDownloadAgent, GameDownloadError}, + download_manager_builder::CurrentProgressObject, progress_object::ProgressObject, queue::Queue, }; @@ -32,6 +34,8 @@ pub enum DownloadManagerSignal { Cancel(String), /// Any error which occurs in the agent Error(GameDownloadError), + /// Pushes UI update + Update, } pub enum DownloadManagerStatus { Downloading, @@ -39,10 +43,12 @@ pub enum DownloadManagerStatus { Empty, Error(GameDownloadError), } + +#[derive(Serialize, Clone)] pub enum GameDownloadStatus { + Queued, Downloading, Paused, - Uninitialised, Error, } @@ -59,18 +65,20 @@ pub enum GameDownloadStatus { pub struct DownloadManager { terminator: JoinHandle>, download_queue: Queue, - progress: Arc>>, + progress: CurrentProgressObject, command_sender: Sender, } -pub struct AgentInterfaceData { +pub struct GameDownloadAgentQueueStandin { pub id: String, pub status: Mutex, + pub progress: Arc, } -impl From> for AgentInterfaceData { +impl From> for GameDownloadAgentQueueStandin { fn from(value: Arc) -> Self { Self { id: value.id.clone(), - status: Mutex::from(GameDownloadStatus::Uninitialised), + status: Mutex::from(GameDownloadStatus::Queued), + progress: value.progress.clone(), } } } @@ -79,7 +87,7 @@ impl DownloadManager { pub fn new( terminator: JoinHandle>, download_queue: Queue, - progress: Arc>>, + progress: CurrentProgressObject, command_sender: Sender, ) -> Self { Self { @@ -109,10 +117,10 @@ impl DownloadManager { .send(DownloadManagerSignal::Cancel(game_id)) .unwrap(); } - pub fn edit(&self) -> MutexGuard<'_, VecDeque>> { + pub fn edit(&self) -> MutexGuard<'_, VecDeque>> { self.download_queue.edit() } - pub fn read_queue(&self) -> VecDeque> { + pub fn read_queue(&self) -> VecDeque> { self.download_queue.read() } pub fn get_current_game_download_progress(&self) -> Option { @@ -157,7 +165,7 @@ impl DownloadManager { /// Takes in the locked value from .edit() and attempts to /// get the index of whatever game_id is passed in fn get_index_from_id( - queue: &mut MutexGuard<'_, VecDeque>>, + queue: &mut MutexGuard<'_, VecDeque>>, id: String, ) -> Option { queue diff --git a/desktop/src-tauri/src/downloads/download_manager_builder.rs b/desktop/src-tauri/src/downloads/download_manager_builder.rs index a1578a50..4809707b 100644 --- a/desktop/src-tauri/src/downloads/download_manager_builder.rs +++ b/desktop/src-tauri/src/downloads/download_manager_builder.rs @@ -7,21 +7,20 @@ use std::{ thread::spawn, }; -use log::{error, info, warn}; -use rustbreak::Database; +use log::{error, info}; use tauri::{AppHandle, Emitter}; use crate::{ db::DatabaseGameStatus, - library::{on_game_complete, GameUpdateEvent}, + library::{on_game_complete, GameUpdateEvent, QueueUpdateEvent, QueueUpdateEventQueueData}, DB, }; use super::{ download_agent::{GameDownloadAgent, GameDownloadError}, download_manager::{ - AgentInterfaceData, DownloadManager, DownloadManagerSignal, DownloadManagerStatus, - GameDownloadStatus, + DownloadManager, DownloadManagerSignal, DownloadManagerStatus, + GameDownloadAgentQueueStandin, GameDownloadStatus, }, download_thread_control_flag::{DownloadThreadControl, DownloadThreadControlFlag}, progress_object::ProgressObject, @@ -65,16 +64,19 @@ Behold, my madness - quexeky */ +// Refactored to consolidate this type. It's a monster. +pub type CurrentProgressObject = Arc>>>; + pub struct DownloadManagerBuilder { download_agent_registry: HashMap>, download_queue: Queue, command_receiver: Receiver, sender: Sender, - progress: Arc>>, + progress: CurrentProgressObject, status: Arc>, app_handle: AppHandle, - current_game_interface: Option>, // Should be the only game download agent in the map with the "Go" flag + current_game_interface: Option>, // Should be the only game download agent in the map with the "Go" flag active_control_flag: Option, } @@ -121,6 +123,21 @@ impl DownloadManagerBuilder { .unwrap(); } + fn push_manager_update(&self) { + let queue = self.download_queue.read(); + let queue_objs: Vec = queue + .iter() + .map(|interface| QueueUpdateEventQueueData { + id: interface.id.clone(), + status: interface.status.lock().unwrap().clone(), + progress: interface.progress.get_progress(), + }) + .collect(); + + let event_data = QueueUpdateEvent { queue: queue_objs }; + self.app_handle.emit("update_queue", event_data).unwrap(); + } + fn manage_queue(mut self) -> Result<(), ()> { loop { let signal = match self.command_receiver.recv() { @@ -153,6 +170,9 @@ impl DownloadManagerBuilder { DownloadManagerSignal::Cancel(id) => { self.manage_cancel_signal(id); } + DownloadManagerSignal::Update => { + self.push_manager_update(); + } }; } } @@ -175,9 +195,18 @@ impl DownloadManagerBuilder { self.active_control_flag = None; *self.progress.lock().unwrap() = None; - on_game_complete(game_id, download_agent.version.clone(), &self.app_handle); + if let Err(error) = + on_game_complete(game_id, download_agent.version.clone(), &self.app_handle) + { + self.sender + .send(DownloadManagerSignal::Error( + GameDownloadError::Communication(error), + )) + .unwrap(); + } } } + self.sender.send(DownloadManagerSignal::Update).unwrap(); self.sender.send(DownloadManagerSignal::Go).unwrap(); } @@ -189,10 +218,11 @@ impl DownloadManagerBuilder { target_download_dir, self.sender.clone(), )); - let agent_status = GameDownloadStatus::Uninitialised; - let interface_data = AgentInterfaceData { + let agent_status = GameDownloadStatus::Queued; + let interface_data = GameDownloadAgentQueueStandin { id: id.clone(), status: Mutex::new(agent_status), + progress: download_agent.progress.clone(), }; let version_name = download_agent.version.clone(); self.download_agent_registry @@ -200,6 +230,7 @@ impl DownloadManagerBuilder { self.download_queue.append(interface_data); self.set_game_status(id, DatabaseGameStatus::Queued { version_name }); + self.sender.send(DownloadManagerSignal::Update).unwrap(); } fn manage_go_signal(&mut self) { @@ -217,6 +248,8 @@ impl DownloadManagerBuilder { .unwrap() .clone(); self.current_game_interface = Some(agent_data); + // Cloning option should be okay because it only clones the Arc inside, not the AgentInterfaceData + let agent_data = self.current_game_interface.clone().unwrap(); let version_name = download_agent.version.clone(); @@ -243,6 +276,11 @@ impl DownloadManagerBuilder { }; }); + // Set status for game + let mut status_handle = agent_data.status.lock().unwrap(); + *status_handle = GameDownloadStatus::Downloading; + + // Set flags for download manager active_control_flag.set(DownloadThreadControlFlag::Go); self.set_status(DownloadManagerStatus::Downloading); self.set_game_status( @@ -260,6 +298,8 @@ impl DownloadManagerBuilder { self.current_game_interface.as_ref().unwrap().id.clone(), DatabaseGameStatus::Remote {}, ); + + self.sender.send(DownloadManagerSignal::Update).unwrap(); } fn manage_cancel_signal(&mut self, game_id: String) { if let Some(current_flag) = &self.active_control_flag { diff --git a/desktop/src-tauri/src/downloads/progress_object.rs b/desktop/src-tauri/src/downloads/progress_object.rs index 9114003f..13b65922 100644 --- a/desktop/src-tauri/src/downloads/progress_object.rs +++ b/desktop/src-tauri/src/downloads/progress_object.rs @@ -1,27 +1,84 @@ use std::{ sync::{ atomic::{AtomicUsize, Ordering}, + mpsc::Sender, Arc, Mutex, }, time::Instant, }; +use log::info; + +use super::download_manager::DownloadManagerSignal; + #[derive(Clone)] pub struct ProgressObject { max: Arc>, progress_instances: Arc>>>, start: Arc>, + sender: Sender, + + points_towards_update: Arc, + points_to_push_update: Arc>, } +pub struct ProgressHandle { + progress: Arc, + progress_object: Arc, +} + +impl ProgressHandle { + pub fn new(progress: Arc, progress_object: Arc) -> Self { + Self { + progress, + progress_object, + } + } + pub fn set(&self, amount: usize) { + self.progress.store(amount, Ordering::Relaxed); + } + pub fn add(&self, amount: usize) { + self.progress + .fetch_add(amount, std::sync::atomic::Ordering::Relaxed); + self.progress_object.check_push_update(amount); + } +} + +static PROGRESS_UPDATES: usize = 100; + impl ProgressObject { - pub fn new(max: usize, length: usize) -> Self { + pub fn new(max: usize, length: usize, sender: Sender) -> Self { let arr = Mutex::new((0..length).map(|_| Arc::new(AtomicUsize::new(0))).collect()); + // TODO: consolidate this calculate with the set_max function below + let points_to_push_update = max / PROGRESS_UPDATES; Self { max: Arc::new(Mutex::new(max)), progress_instances: Arc::new(arr), start: Arc::new(Mutex::new(Instant::now())), + sender, + + points_towards_update: Arc::new(AtomicUsize::new(0)), + points_to_push_update: Arc::new(Mutex::new(points_to_push_update)), } } + + pub fn check_push_update(&self, amount_added: usize) { + let current_amount = self + .points_towards_update + .fetch_add(amount_added, Ordering::Relaxed); + + let to_update_handle = self.points_to_push_update.lock().unwrap(); + let to_update = to_update_handle.clone(); + drop(to_update_handle); + + if current_amount < to_update { + return; + } + self.points_towards_update + .fetch_sub(to_update, Ordering::Relaxed); + self.sender.send(DownloadManagerSignal::Update).unwrap(); + } + pub fn set_time_now(&self) { *self.start.lock().unwrap() = Instant::now(); } @@ -37,13 +94,14 @@ impl ProgressObject { *self.max.lock().unwrap() } pub fn set_max(&self, new_max: usize) { - *self.max.lock().unwrap() = new_max + *self.max.lock().unwrap() = new_max; + *self.points_to_push_update.lock().unwrap() = new_max / PROGRESS_UPDATES; + info!("points to push update: {}", new_max / PROGRESS_UPDATES); } pub fn set_size(&self, length: usize) { *self.progress_instances.lock().unwrap() = (0..length).map(|_| Arc::new(AtomicUsize::new(0))).collect(); } - pub fn get_progress(&self) -> f64 { self.sum() as f64 / self.get_max() as f64 } diff --git a/desktop/src-tauri/src/downloads/queue.rs b/desktop/src-tauri/src/downloads/queue.rs index 80e3dc02..f4139aca 100644 --- a/desktop/src-tauri/src/downloads/queue.rs +++ b/desktop/src-tauri/src/downloads/queue.rs @@ -3,11 +3,11 @@ use std::{ sync::{Arc, Mutex, MutexGuard}, }; -use super::download_manager::AgentInterfaceData; +use super::download_manager::GameDownloadAgentQueueStandin; #[derive(Clone)] pub struct Queue { - inner: Arc>>>, + inner: Arc>>>, } impl Queue { @@ -16,13 +16,13 @@ impl Queue { inner: Arc::new(Mutex::new(VecDeque::new())), } } - pub fn read(&self) -> VecDeque> { + pub fn read(&self) -> VecDeque> { self.inner.lock().unwrap().clone() } - pub fn edit(&self) -> MutexGuard<'_, VecDeque>> { + pub fn edit(&self) -> MutexGuard<'_, VecDeque>> { self.inner.lock().unwrap() } - pub fn pop_front(&self) -> Option> { + pub fn pop_front(&self) -> Option> { self.edit().pop_front() } pub fn empty(&self) -> bool { @@ -30,17 +30,17 @@ impl Queue { } /// Either inserts `interface` at the specified index, or appends to /// the back of the deque if index is greater than the length of the deque - pub fn insert(&self, interface: AgentInterfaceData, index: usize) { + pub fn insert(&self, interface: GameDownloadAgentQueueStandin, index: usize) { if self.read().len() > index { self.append(interface); } else { self.edit().insert(index, Arc::new(interface)); } } - pub fn append(&self, interface: AgentInterfaceData) { + pub fn append(&self, interface: GameDownloadAgentQueueStandin) { self.edit().push_back(Arc::new(interface)); } - pub fn pop_front_if_equal(&self, game_id: String) -> Option> { + pub fn pop_front_if_equal(&self, game_id: String) -> Option> { let mut queue = self.edit(); let front = match queue.front() { Some(front) => front, diff --git a/desktop/src-tauri/src/library.rs b/desktop/src-tauri/src/library.rs index 041cf2ef..9b38c5de 100644 --- a/desktop/src-tauri/src/library.rs +++ b/desktop/src-tauri/src/library.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::{HashMap, VecDeque}; use std::fmt::format; use std::sync::Mutex; @@ -42,6 +42,18 @@ pub struct GameUpdateEvent { pub status: DatabaseGameStatus, } +#[derive(Serialize, Clone)] +pub struct QueueUpdateEventQueueData { + pub id: String, + pub status: GameDownloadStatus, + pub progress: f64, +} + +#[derive(serde::Serialize, Clone)] +pub struct QueueUpdateEvent { + pub queue: Vec, +} + // Game version with some fields missing and size information #[derive(serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] From 74f071281e9e861bb66b0930de4f3c71e5b92a2e Mon Sep 17 00:00:00 2001 From: DecDuck Date: Sun, 8 Dec 2024 12:33:45 +1100 Subject: [PATCH 26/31] feat(download widget): download widget and queue fix --- desktop/.gitignore | 2 +- desktop/components/Header.vue | 46 +++++++++-------- desktop/components/HeaderQueueWidget.vue | 17 +++++++ desktop/composables/queue.ts | 2 +- desktop/layouts/default.vue | 2 - desktop/pages/library/[id]/index.vue | 2 +- .../src-tauri/src/downloads/download_logic.rs | 6 ++- .../src/downloads/download_manager_builder.rs | 51 ++++++++++++------- .../src/downloads/progress_object.rs | 2 +- 9 files changed, 85 insertions(+), 45 deletions(-) create mode 100644 desktop/components/HeaderQueueWidget.vue diff --git a/desktop/.gitignore b/desktop/.gitignore index 8e8efd66..5767a3f2 100644 --- a/desktop/.gitignore +++ b/desktop/.gitignore @@ -26,4 +26,4 @@ dist-ssr .output src-tauri/flamegraph.svg -src-tuair/perf* \ No newline at end of file +src-tauri/perf* \ No newline at end of file diff --git a/desktop/components/Header.vue b/desktop/components/Header.vue index 33ee5062..e91edf54 100644 --- a/desktop/components/Header.vue +++ b/desktop/components/Header.vue @@ -1,53 +1,58 @@ diff --git a/desktop/components/HeaderQueueWidget.vue b/desktop/components/HeaderQueueWidget.vue new file mode 100644 index 00000000..105d14e8 --- /dev/null +++ b/desktop/components/HeaderQueueWidget.vue @@ -0,0 +1,17 @@ + + + diff --git a/desktop/composables/queue.ts b/desktop/composables/queue.ts index 931d3306..f6848528 100644 --- a/desktop/composables/queue.ts +++ b/desktop/composables/queue.ts @@ -1,7 +1,7 @@ import { listen } from "@tauri-apps/api/event"; export type QueueState = { - queue: Array<{ id: string; status: string }>; + queue: Array<{ id: string; status: string, progress: number }>; }; export const useQueueState = () => diff --git a/desktop/layouts/default.vue b/desktop/layouts/default.vue index af9826c2..89dd49b5 100644 --- a/desktop/layouts/default.vue +++ b/desktop/layouts/default.vue @@ -2,8 +2,6 @@
- {{ queueState }} -
diff --git a/desktop/pages/library/[id]/index.vue b/desktop/pages/library/[id]/index.vue index 9d914ab2..1ede1238 100644 --- a/desktop/pages/library/[id]/index.vue +++ b/desktop/pages/library/[id]/index.vue @@ -227,7 +227,7 @@ : 'font-normal', 'block truncate', ]" - >{{ dir }}}{{ dir }} { // Write automatically pushes to file and hasher impl Write for DropWriter { fn write(&mut self, buf: &[u8]) -> io::Result { + /* self.hasher.write_all(buf).map_err(|e| { io::Error::new( ErrorKind::Other, format!("Unable to write to hasher: {}", e), ) })?; + */ self.destination.write(buf) } fn flush(&mut self) -> io::Result<()> { - self.hasher.flush()?; + // self.hasher.flush()?; self.destination.flush() } } @@ -185,6 +187,7 @@ pub fn download_game_chunk( return Ok(false); }; + /* let checksum = pipeline .finish() .map_err(|e| GameDownloadError::IoError(e))?; @@ -193,6 +196,7 @@ pub fn download_game_chunk( if res != ctx.checksum { return Err(GameDownloadError::Checksum); } + */ Ok(true) } diff --git a/desktop/src-tauri/src/downloads/download_manager_builder.rs b/desktop/src-tauri/src/downloads/download_manager_builder.rs index 4809707b..fa3e13ff 100644 --- a/desktop/src-tauri/src/downloads/download_manager_builder.rs +++ b/desktop/src-tauri/src/downloads/download_manager_builder.rs @@ -76,7 +76,7 @@ pub struct DownloadManagerBuilder { status: Arc>, app_handle: AppHandle, - current_game_interface: Option>, // Should be the only game download agent in the map with the "Go" flag + current_download_agent: Option>, // Should be the only game download agent in the map with the "Go" flag active_control_flag: Option, } @@ -91,7 +91,7 @@ impl DownloadManagerBuilder { download_agent_registry: HashMap::new(), download_queue: queue.clone(), command_receiver, - current_game_interface: None, + current_download_agent: None, active_control_flag: None, status: status.clone(), sender: command_sender.clone(), @@ -138,6 +138,16 @@ impl DownloadManagerBuilder { self.app_handle.emit("update_queue", event_data).unwrap(); } + fn cleanup_current_game(&mut self, game_id: &String) -> Arc { + self.download_queue.pop_front(); + let download_agent = self.download_agent_registry.remove(game_id).unwrap(); + self.active_control_flag = None; + *self.progress.lock().unwrap() = None; + self.current_download_agent = None; + + return download_agent; + } + fn manage_queue(mut self) -> Result<(), ()> { loop { let signal = match self.command_receiver.recv() { @@ -186,14 +196,11 @@ impl DownloadManagerBuilder { fn manage_completed_signal(&mut self, game_id: String) { info!("Got signal 'Completed'"); - if let Some(interface) = &self.current_game_interface { + if let Some(interface) = &self.current_download_agent { // When if let chains are stabilised, combine these two statements if interface.id == game_id { info!("Popping consumed data"); - self.download_queue.pop_front(); - let download_agent = self.download_agent_registry.remove(&game_id).unwrap(); - self.active_control_flag = None; - *self.progress.lock().unwrap() = None; + let download_agent = self.cleanup_current_game(&game_id); if let Err(error) = on_game_complete(game_id, download_agent.version.clone(), &self.app_handle) @@ -240,6 +247,11 @@ impl DownloadManagerBuilder { return; } + if self.current_download_agent.is_some() { + info!("skipping go signal due to existing download job"); + return; + } + info!("Starting download agent"); let agent_data = self.download_queue.read().front().unwrap().clone(); let download_agent = self @@ -247,9 +259,9 @@ impl DownloadManagerBuilder { .get(&agent_data.id) .unwrap() .clone(); - self.current_game_interface = Some(agent_data); + self.current_download_agent = Some(agent_data); // Cloning option should be okay because it only clones the Arc inside, not the AgentInterfaceData - let agent_data = self.current_game_interface.clone().unwrap(); + let agent_data = self.current_download_agent.clone().unwrap(); let version_name = download_agent.version.clone(); @@ -284,31 +296,34 @@ impl DownloadManagerBuilder { active_control_flag.set(DownloadThreadControlFlag::Go); self.set_status(DownloadManagerStatus::Downloading); self.set_game_status( - self.current_game_interface.as_ref().unwrap().id.clone(), + self.current_download_agent.as_ref().unwrap().id.clone(), DatabaseGameStatus::Downloading { version_name }, ); } - fn manage_error_signal(&self, error: GameDownloadError) { - let current_status = self.current_game_interface.clone().unwrap(); + fn manage_error_signal(&mut self, error: GameDownloadError) { + let current_status = self.current_download_agent.clone().unwrap(); + + self.cleanup_current_game(¤t_status.id); // Remove all the locks and shit + let mut lock = current_status.status.lock().unwrap(); *lock = GameDownloadStatus::Error; self.set_status(DownloadManagerStatus::Error(error)); - self.set_game_status( - self.current_game_interface.as_ref().unwrap().id.clone(), - DatabaseGameStatus::Remote {}, - ); + let game_id = self.current_download_agent.as_ref().unwrap().id.clone(); + self.set_game_status(game_id, DatabaseGameStatus::Remote {}); self.sender.send(DownloadManagerSignal::Update).unwrap(); } fn manage_cancel_signal(&mut self, game_id: String) { if let Some(current_flag) = &self.active_control_flag { current_flag.set(DownloadThreadControlFlag::Stop); - self.active_control_flag = None; - *self.progress.lock().unwrap() = None; } // TODO wait until current download exits + // This cleanup function might break things because it + // unsets the control flag + self.cleanup_current_game(&game_id); + self.download_agent_registry.remove(&game_id); let mut lock = self.download_queue.edit(); let index = match lock.iter().position(|interface| interface.id == game_id) { diff --git a/desktop/src-tauri/src/downloads/progress_object.rs b/desktop/src-tauri/src/downloads/progress_object.rs index 13b65922..fac9b9b0 100644 --- a/desktop/src-tauri/src/downloads/progress_object.rs +++ b/desktop/src-tauri/src/downloads/progress_object.rs @@ -49,7 +49,7 @@ static PROGRESS_UPDATES: usize = 100; impl ProgressObject { pub fn new(max: usize, length: usize, sender: Sender) -> Self { let arr = Mutex::new((0..length).map(|_| Arc::new(AtomicUsize::new(0))).collect()); - // TODO: consolidate this calculate with the set_max function below + // TODO: consolidate this calculation with the set_max function below let points_to_push_update = max / PROGRESS_UPDATES; Self { max: Arc::new(Mutex::new(max)), From ff39f1ca39cca49326f6a423c01d92e2174ae494 Mon Sep 17 00:00:00 2001 From: DecDuck Date: Sun, 8 Dec 2024 12:55:41 +1100 Subject: [PATCH 27/31] fix: remove unnecessary unstable feature --- desktop/src-tauri/src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs index ef307880..0d9a505c 100644 --- a/desktop/src-tauri/src/lib.rs +++ b/desktop/src-tauri/src/lib.rs @@ -1,5 +1,3 @@ -#![feature(map_try_insert)] - mod auth; mod db; mod downloads; From 3d8639136b6380dab6f9d12a3ab219df4d1c04a2 Mon Sep 17 00:00:00 2001 From: DecDuck Date: Mon, 9 Dec 2024 17:03:48 +1100 Subject: [PATCH 28/31] feat(download ui): debug queue interface --- desktop/components/Header.vue | 1 - desktop/components/HeaderQueueWidget.vue | 25 ++- desktop/composables/game.ts | 31 ++++ desktop/composables/queue.ts | 2 +- desktop/package.json | 5 +- desktop/pages/library.vue | 6 +- desktop/pages/library/[id]/index.vue | 18 +-- desktop/pages/queue.vue | 24 +++ desktop/plugins/vuedraggable.ts | 5 + desktop/prisma/schema.prisma | 150 ------------------ .../src/downloads/download_commands.rs | 28 ++-- .../src/downloads/download_manager.rs | 4 + desktop/src-tauri/src/lib.rs | 2 +- desktop/src-tauri/src/library.rs | 19 ++- desktop/types.ts | 21 ++- desktop/yarn.lock | 64 ++------ 16 files changed, 148 insertions(+), 257 deletions(-) create mode 100644 desktop/composables/game.ts create mode 100644 desktop/pages/queue.vue create mode 100644 desktop/plugins/vuedraggable.ts delete mode 100644 desktop/prisma/schema.prisma diff --git a/desktop/components/Header.vue b/desktop/components/Header.vue index e91edf54..75537eb1 100644 --- a/desktop/components/Header.vue +++ b/desktop/components/Header.vue @@ -29,7 +29,6 @@
  1. diff --git a/desktop/components/HeaderQueueWidget.vue b/desktop/components/HeaderQueueWidget.vue index 105d14e8..23c5ad5a 100644 --- a/desktop/components/HeaderQueueWidget.vue +++ b/desktop/components/HeaderQueueWidget.vue @@ -1,17 +1,26 @@ diff --git a/desktop/composables/game.ts b/desktop/composables/game.ts new file mode 100644 index 00000000..5eba1d9d --- /dev/null +++ b/desktop/composables/game.ts @@ -0,0 +1,31 @@ +import { invoke } from "@tauri-apps/api/core"; +import { listen } from "@tauri-apps/api/event"; +import type { Game, GameStatus } from "~/types"; + +const gameRegistry: { [key: string]: Game } = {}; + +const gameStatusRegistry: { [key: string]: Ref } = {}; + +export const useGame = async (id: string) => { + if (!gameRegistry[id]) { + const data: { game: Game; status: GameStatus } = await invoke( + "fetch_game", + { + id, + } + ); + gameRegistry[id] = data.game; + if (!gameStatusRegistry[id]) { + gameStatusRegistry[id] = ref(data.status); + + listen(`update_game/${id}`, (event) => { + const payload: { status: GameStatus } = event.payload as any; + gameStatusRegistry[id].value = payload.status; + }); + } + } + + const game = gameRegistry[id]; + const status = gameStatusRegistry[id]; + return { game, status }; +}; diff --git a/desktop/composables/queue.ts b/desktop/composables/queue.ts index f6848528..04872608 100644 --- a/desktop/composables/queue.ts +++ b/desktop/composables/queue.ts @@ -1,7 +1,7 @@ import { listen } from "@tauri-apps/api/event"; export type QueueState = { - queue: Array<{ id: string; status: string, progress: number }>; + queue: Array<{ id: string; status: string, progress: number | null }>; }; export const useQueueState = () => diff --git a/desktop/package.json b/desktop/package.json index 49c2009f..c93e9a49 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -14,7 +14,6 @@ "dependencies": { "@headlessui/vue": "^1.7.23", "@heroicons/vue": "^2.1.5", - "@prisma/client": "5.20.0", "@tauri-apps/api": ">=2.0.0", "@tauri-apps/plugin-deep-link": "~2", "@tauri-apps/plugin-dialog": "^2.0.1", @@ -22,14 +21,14 @@ "nuxt": "^3.13.0", "scss": "^0.2.4", "vue": "latest", - "vue-router": "latest" + "vue-router": "latest", + "vuedraggable": "^4.1.0" }, "devDependencies": { "@tailwindcss/forms": "^0.5.9", "@tauri-apps/cli": ">=2.0.0", "autoprefixer": "^10.4.20", "postcss": "^8.4.47", - "prisma": "^5.20.0", "sass-embedded": "^1.79.4", "tailwindcss": "^3.4.13" }, diff --git a/desktop/pages/library.vue b/desktop/pages/library.vue index c91ea64d..ac7e24f5 100644 --- a/desktop/pages/library.vue +++ b/desktop/pages/library.vue @@ -40,12 +40,10 @@ diff --git a/desktop/plugins/vuedraggable.ts b/desktop/plugins/vuedraggable.ts new file mode 100644 index 00000000..58d50a0b --- /dev/null +++ b/desktop/plugins/vuedraggable.ts @@ -0,0 +1,5 @@ +import draggable from "vuedraggable"; + +export default defineNuxtPlugin((nuxtApp) => { + nuxtApp.vueApp.component("draggable", draggable); +}); diff --git a/desktop/prisma/schema.prisma b/desktop/prisma/schema.prisma deleted file mode 100644 index 1ca51a1f..00000000 --- a/desktop/prisma/schema.prisma +++ /dev/null @@ -1,150 +0,0 @@ -// This should be copied from the main Drop repo -// TODO: do this automatically - -generator client { - provider = "prisma-client-js" -} - -datasource db { - provider = "postgresql" - url = env("DATABASE_URL") -} - -model User { - id String @id @default(uuid()) - username String @unique - admin Boolean @default(false) - - email String - displayName String - profilePicture String // Object - - authMecs LinkedAuthMec[] - clients Client[] -} - -enum AuthMec { - Simple -} - -model LinkedAuthMec { - userId String - mec AuthMec - - credentials Json - - user User @relation(fields: [userId], references: [id]) - - @@id([userId, mec]) -} - -enum ClientCapabilities { - DownloadAggregation -} - -enum Platform { - Windows @map("windows") - Linux @map("linux") -} - -// References a device -model Client { - id String @id @default(uuid()) - userId String - user User @relation(fields: [userId], references: [id]) - - endpoint String - capabilities ClientCapabilities[] - - name String - platform Platform - lastConnected DateTime -} - -enum MetadataSource { - Custom - GiantBomb -} - -model Game { - id String @id @default(uuid()) - - metadataSource MetadataSource - metadataId String - - // Any field prefixed with m is filled in from metadata - // Acts as a cache so we can search and filter it - mName String // Name of game - mShortDescription String // Short description - mDescription String // Supports markdown - mDevelopers Developer[] - mPublishers Publisher[] - - mReviewCount Int - mReviewRating Float - - mIconId String // linked to objects in s3 - mBannerId String // linked to objects in s3 - mCoverId String - mImageLibrary String[] // linked to objects in s3 - - versions GameVersion[] - libraryBasePath String @unique // Base dir for all the game versions - - @@unique([metadataSource, metadataId], name: "metadataKey") -} - -// A particular set of files that relate to the version -model GameVersion { - gameId String - game Game @relation(fields: [gameId], references: [id]) - versionName String // Sub directory for the game files - - platform Platform - launchCommand String // Command to run to start. Platform-specific. Windows games on Linux will wrap this command in Proton/Wine - setupCommand String // Command to setup game (dependencies and such) - dropletManifest Json // Results from droplet - - versionIndex Int - delta Boolean @default(false) - - @@id([gameId, versionName]) -} - -model Developer { - id String @id @default(uuid()) - - metadataSource MetadataSource - metadataId String - metadataOriginalQuery String - - mName String - mShortDescription String - mDescription String - mLogo String - mBanner String - mWebsite String - - games Game[] - - @@unique([metadataSource, metadataId, metadataOriginalQuery], name: "metadataKey") -} - -model Publisher { - id String @id @default(uuid()) - - metadataSource MetadataSource - metadataId String - metadataOriginalQuery String - - mName String - mShortDescription String - mDescription String - mLogo String - mBanner String - mWebsite String - - games Game[] - - @@unique([metadataSource, metadataId, metadataOriginalQuery], name: "metadataKey") -} diff --git a/desktop/src-tauri/src/downloads/download_commands.rs b/desktop/src-tauri/src/downloads/download_commands.rs index 044a0677..d5ef6863 100644 --- a/desktop/src-tauri/src/downloads/download_commands.rs +++ b/desktop/src-tauri/src/downloads/download_commands.rs @@ -19,21 +19,6 @@ pub fn download_game( .map_err(|_| "An error occurred while communicating with the download manager.".to_string()) } -#[tauri::command] -pub fn get_current_game_download_progress( - state: tauri::State<'_, Mutex>, -) -> Result { - match state - .lock() - .unwrap() - .download_manager - .get_current_game_download_progress() - { - Some(progress) => Ok(progress), - None => Err("Game does not exist".to_string()), - } -} - #[tauri::command] pub fn cancel_game_download(state: tauri::State<'_, Mutex>, game_id: String) { info!("Cancelling game download {}", game_id); @@ -54,6 +39,19 @@ pub fn resume_game_downloads(state: tauri::State<'_, Mutex>) { state.lock().unwrap().download_manager.resume_downloads() } +#[tauri::command] +pub fn move_game_in_queue( + state: tauri::State<'_, Mutex>, + old_index: usize, + new_index: usize, +) { + state + .lock() + .unwrap() + .download_manager + .rearrange(old_index, new_index) +} + #[tauri::command] pub fn get_current_write_speed(state: tauri::State<'_, Mutex>) {} diff --git a/desktop/src-tauri/src/downloads/download_manager.rs b/desktop/src-tauri/src/downloads/download_manager.rs index e70d53db..79c10062 100644 --- a/desktop/src-tauri/src/downloads/download_manager.rs +++ b/desktop/src-tauri/src/downloads/download_manager.rs @@ -132,19 +132,23 @@ impl DownloadManager { let current_index = get_index_from_id(&mut queue, id).unwrap(); let to_move = queue.remove(current_index).unwrap(); queue.insert(new_index, to_move); + self.command_sender.send(DownloadManagerSignal::Update); } pub fn rearrange(&self, current_index: usize, new_index: usize) { let mut queue = self.edit(); let to_move = queue.remove(current_index).unwrap(); queue.insert(new_index, to_move); + self.command_sender.send(DownloadManagerSignal::Update); } pub fn remove_from_queue(&self, index: usize) { self.edit().remove(index); + self.command_sender.send(DownloadManagerSignal::Update); } pub fn remove_from_queue_string(&self, id: String) { let mut queue = self.edit(); let current_index = get_index_from_id(&mut queue, id).unwrap(); queue.remove(current_index); + self.command_sender.send(DownloadManagerSignal::Update); } pub fn pause_downloads(&self) { self.command_sender diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs index 0d9a505c..87850072 100644 --- a/desktop/src-tauri/src/lib.rs +++ b/desktop/src-tauri/src/lib.rs @@ -134,7 +134,7 @@ pub fn run() { fetch_game_verion_options, // Downloads download_game, - get_current_game_download_progress, + move_game_in_queue, cancel_game_download, pause_game_downloads, resume_game_downloads, diff --git a/desktop/src-tauri/src/library.rs b/desktop/src-tauri/src/library.rs index 9b38c5de..139b04c7 100644 --- a/desktop/src-tauri/src/library.rs +++ b/desktop/src-tauri/src/library.rs @@ -17,7 +17,7 @@ use crate::remote::RemoteAccessError; use crate::{auth::generate_authorization_header, AppState, DB}; #[derive(serde::Serialize)] -struct FetchGameStruct { +pub struct FetchGameStruct { game: Game, status: DatabaseGameStatus, } @@ -67,7 +67,7 @@ pub struct GameVersionOption { // total_size: usize, } -fn fetch_library_logic(app: AppHandle) -> Result { +fn fetch_library_logic(app: AppHandle) -> Result, RemoteAccessError> { let base_url = DB.fetch_base_url(); let library_url = base_url.join("/api/v1/client/user/library")?; @@ -102,15 +102,18 @@ fn fetch_library_logic(app: AppHandle) -> Result { drop(handle); - Ok(json!(games.clone()).to_string()) + Ok(games) } #[tauri::command] -pub fn fetch_library(app: AppHandle) -> Result { +pub fn fetch_library(app: AppHandle) -> Result, String> { fetch_library_logic(app).map_err(|e| e.to_string()) } -fn fetch_game_logic(id: String, app: tauri::AppHandle) -> Result { +fn fetch_game_logic( + id: String, + app: tauri::AppHandle, +) -> Result { let state = app.state::>(); let mut state_handle = state.lock().unwrap(); @@ -128,7 +131,7 @@ fn fetch_game_logic(id: String, app: tauri::AppHandle) -> Result Result Result { +pub fn fetch_game(id: String, app: tauri::AppHandle) -> Result { let result = fetch_game_logic(id, app); if result.is_err() { diff --git a/desktop/types.ts b/desktop/types.ts index 3a6e3f46..5702594c 100644 --- a/desktop/types.ts +++ b/desktop/types.ts @@ -1,4 +1,3 @@ -import type { User } from "@prisma/client"; import type { Component } from "vue"; export type NavigationItem = { @@ -12,11 +11,31 @@ export type QuickActionNav = { notifications?: number; action: () => Promise; }; + +export type User = { + id: string; + username: string; + admin: boolean; + displayName: string; + profilePicture: string; +}; + export type AppState = { status: AppStatus; user?: User; }; +export type Game = { + id: string; + mName: string; + mShortDescription: string; + mDescription: string; + mIconId: string; + mBannerId: string; + mCoverId: string; + mImageLibrary: string[]; +}; + export enum AppStatus { NotConfigured = "NotConfigured", SignedOut = "SignedOut", diff --git a/desktop/yarn.lock b/desktop/yarn.lock index af475d71..a4fa8b8d 100644 --- a/desktop/yarn.lock +++ b/desktop/yarn.lock @@ -1075,47 +1075,6 @@ resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.28.tgz#d45e01c4a56f143ee69c54dd6b12eade9e270a73" integrity sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw== -"@prisma/client@5.20.0": - version "5.20.0" - resolved "https://registry.yarnpkg.com/@prisma/client/-/client-5.20.0.tgz#4fc9f2b2341c9c997c139df4445688dd6b39663b" - integrity sha512-CLv55ZuMuUawMsxoqxGtLT3bEZoa2W8L3Qnp6rDIFWy+ZBrUcOFKdoeGPSnbBqxc3SkdxJrF+D1veN/WNynZYA== - -"@prisma/debug@5.20.0": - version "5.20.0" - resolved "https://registry.yarnpkg.com/@prisma/debug/-/debug-5.20.0.tgz#c6d1cf6e3c6e9dba150347f13ca200b1d66cc9fc" - integrity sha512-oCx79MJ4HSujokA8S1g0xgZUGybD4SyIOydoHMngFYiwEwYDQ5tBQkK5XoEHuwOYDKUOKRn/J0MEymckc4IgsQ== - -"@prisma/engines-version@5.20.0-12.06fc58a368dc7be9fbbbe894adf8d445d208c284": - version "5.20.0-12.06fc58a368dc7be9fbbbe894adf8d445d208c284" - resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-5.20.0-12.06fc58a368dc7be9fbbbe894adf8d445d208c284.tgz#9a53b13cdcfd706ae54198111000f33c63655c39" - integrity sha512-Lg8AS5lpi0auZe2Mn4gjuCg081UZf88k3cn0RCwHgR+6cyHHpttPZBElJTHf83ZGsRNAmVCZCfUGA57WB4u4JA== - -"@prisma/engines@5.20.0": - version "5.20.0" - resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-5.20.0.tgz#86fe407e55219d33d03ebc26dc829a422faed545" - integrity sha512-DtqkP+hcZvPEbj8t8dK5df2b7d3B8GNauKqaddRRqQBBlgkbdhJkxhoJTrOowlS3vaRt2iMCkU0+CSNn0KhqAQ== - dependencies: - "@prisma/debug" "5.20.0" - "@prisma/engines-version" "5.20.0-12.06fc58a368dc7be9fbbbe894adf8d445d208c284" - "@prisma/fetch-engine" "5.20.0" - "@prisma/get-platform" "5.20.0" - -"@prisma/fetch-engine@5.20.0": - version "5.20.0" - resolved "https://registry.yarnpkg.com/@prisma/fetch-engine/-/fetch-engine-5.20.0.tgz#b917880fb08f654981f14ca49923031b39683586" - integrity sha512-JVcaPXC940wOGpCOwuqQRTz6I9SaBK0c1BAyC1pcz9xBi+dzFgUu3G/p9GV1FhFs9OKpfSpIhQfUJE9y00zhqw== - dependencies: - "@prisma/debug" "5.20.0" - "@prisma/engines-version" "5.20.0-12.06fc58a368dc7be9fbbbe894adf8d445d208c284" - "@prisma/get-platform" "5.20.0" - -"@prisma/get-platform@5.20.0": - version "5.20.0" - resolved "https://registry.yarnpkg.com/@prisma/get-platform/-/get-platform-5.20.0.tgz#c1a53a8d8af67f2b4a6b97dd4d25b1c603236804" - integrity sha512-8/+CehTZZNzJlvuryRgc77hZCWrUDYd/PmlZ7p2yNXtmf2Una4BWnTbak3us6WVdqoz5wmptk6IhsXdG2v5fmA== - dependencies: - "@prisma/debug" "5.20.0" - "@rollup/plugin-alias@^5.1.0": version "5.1.1" resolved "https://registry.yarnpkg.com/@rollup/plugin-alias/-/plugin-alias-5.1.1.tgz#53601d88cda8b1577aa130b4a6e452283605bf26" @@ -2816,7 +2775,7 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@2.3.3, fsevents@~2.3.2, fsevents@~2.3.3: +fsevents@~2.3.2, fsevents@~2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== @@ -4345,15 +4304,6 @@ pretty-bytes@^6.1.1: resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-6.1.1.tgz#38cd6bb46f47afbf667c202cfc754bffd2016a3b" integrity sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ== -prisma@^5.20.0: - version "5.20.0" - resolved "https://registry.yarnpkg.com/prisma/-/prisma-5.20.0.tgz#f2ab266a0d59383506886e7acbff0dbf322f4c7e" - integrity sha512-6obb3ucKgAnsGS9x9gLOe8qa51XxvJ3vLQtmyf52CTey1Qcez3A6W6ROH5HIz5Q5bW+0VpmZb8WBohieMFGpig== - dependencies: - "@prisma/engines" "5.20.0" - optionalDependencies: - fsevents "2.3.3" - process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -4847,6 +4797,11 @@ smob@^1.0.0: resolved "https://registry.yarnpkg.com/smob/-/smob-1.5.0.tgz#85d79a1403abf128d24d3ebc1cdc5e1a9548d3ab" integrity sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig== +sortablejs@1.14.0: + version "1.14.0" + resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.14.0.tgz#6d2e17ccbdb25f464734df621d4f35d4ab35b3d8" + integrity sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w== + source-map-js@^1.0.1, source-map-js@^1.2.0, source-map-js@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" @@ -5553,6 +5508,13 @@ vue@^3.5.5, vue@latest: "@vue/server-renderer" "3.5.11" "@vue/shared" "3.5.11" +vuedraggable@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/vuedraggable/-/vuedraggable-4.1.0.tgz#edece68adb8a4d9e06accff9dfc9040e66852270" + integrity sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww== + dependencies: + sortablejs "1.14.0" + webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" From 113235be2fdff9e5b743e7ed69531ad0884dd4c3 Mon Sep 17 00:00:00 2001 From: DecDuck Date: Mon, 9 Dec 2024 18:07:41 +1100 Subject: [PATCH 29/31] fix(download manager): fixed queue manipulation and waiting for downloads --- .../src-tauri/src/downloads/download_agent.rs | 2 +- .../src/downloads/download_commands.rs | 10 --- .../src/downloads/download_manager.rs | 48 ++++++++----- .../src/downloads/download_manager_builder.rs | 69 +++++++++---------- desktop/src-tauri/src/lib.rs | 1 - 5 files changed, 65 insertions(+), 65 deletions(-) diff --git a/desktop/src-tauri/src/downloads/download_agent.rs b/desktop/src-tauri/src/downloads/download_agent.rs index 0b429d12..a5b2d7de 100644 --- a/desktop/src-tauri/src/downloads/download_agent.rs +++ b/desktop/src-tauri/src/downloads/download_agent.rs @@ -285,7 +285,7 @@ impl GameDownloadAgent { // If we're not out of contexts, we're not done, so we don't fire completed if !context_lock.is_empty() { - info!("Download agent didn't finish, not sending completed signal"); + info!("da for {} exited without completing", self.id.clone()); return Ok(()); } diff --git a/desktop/src-tauri/src/downloads/download_commands.rs b/desktop/src-tauri/src/downloads/download_commands.rs index d5ef6863..1bf1dee6 100644 --- a/desktop/src-tauri/src/downloads/download_commands.rs +++ b/desktop/src-tauri/src/downloads/download_commands.rs @@ -19,16 +19,6 @@ pub fn download_game( .map_err(|_| "An error occurred while communicating with the download manager.".to_string()) } -#[tauri::command] -pub fn cancel_game_download(state: tauri::State<'_, Mutex>, game_id: String) { - info!("Cancelling game download {}", game_id); - state - .lock() - .unwrap() - .download_manager - .cancel_download(game_id); -} - #[tauri::command] pub fn pause_game_downloads(state: tauri::State<'_, Mutex>) { state.lock().unwrap().download_manager.pause_downloads() diff --git a/desktop/src-tauri/src/downloads/download_manager.rs b/desktop/src-tauri/src/downloads/download_manager.rs index 79c10062..9f8799b1 100644 --- a/desktop/src-tauri/src/downloads/download_manager.rs +++ b/desktop/src-tauri/src/downloads/download_manager.rs @@ -1,6 +1,7 @@ use std::{ any::Any, collections::VecDeque, + fmt::Debug, sync::{ mpsc::{SendError, Sender}, Arc, Mutex, MutexGuard, @@ -31,7 +32,7 @@ pub enum DownloadManagerSignal { /// Tells the Manager to stop the current /// download and return Finish, - Cancel(String), + Cancel, /// Any error which occurs in the agent Error(GameDownloadError), /// Pushes UI update @@ -82,6 +83,13 @@ impl From> for GameDownloadAgentQueueStandin { } } } +impl Debug for GameDownloadAgentQueueStandin { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("GameDownloadAgentQueueStandin") + .field("id", &self.id) + .finish() + } +} impl DownloadManager { pub fn new( @@ -112,11 +120,6 @@ impl DownloadManager { ))?; self.command_sender.send(DownloadManagerSignal::Go) } - pub fn cancel_download(&self, game_id: String) { - self.command_sender - .send(DownloadManagerSignal::Cancel(game_id)) - .unwrap(); - } pub fn edit(&self) -> MutexGuard<'_, VecDeque>> { self.download_queue.edit() } @@ -132,23 +135,32 @@ impl DownloadManager { let current_index = get_index_from_id(&mut queue, id).unwrap(); let to_move = queue.remove(current_index).unwrap(); queue.insert(new_index, to_move); - self.command_sender.send(DownloadManagerSignal::Update); + self.command_sender + .send(DownloadManagerSignal::Update) + .unwrap(); } pub fn rearrange(&self, current_index: usize, new_index: usize) { + let needs_pause = current_index == 0 || new_index == 0; + if needs_pause { + self.command_sender + .send(DownloadManagerSignal::Cancel) + .unwrap(); + } + + info!("moving {} to {}", current_index, new_index); + let mut queue = self.edit(); let to_move = queue.remove(current_index).unwrap(); queue.insert(new_index, to_move); - self.command_sender.send(DownloadManagerSignal::Update); - } - pub fn remove_from_queue(&self, index: usize) { - self.edit().remove(index); - self.command_sender.send(DownloadManagerSignal::Update); - } - pub fn remove_from_queue_string(&self, id: String) { - let mut queue = self.edit(); - let current_index = get_index_from_id(&mut queue, id).unwrap(); - queue.remove(current_index); - self.command_sender.send(DownloadManagerSignal::Update); + + info!("new queue: {:?}", queue); + + if needs_pause { + self.command_sender.send(DownloadManagerSignal::Go).unwrap(); + } + self.command_sender + .send(DownloadManagerSignal::Update) + .unwrap(); } pub fn pause_downloads(&self) { self.command_sender diff --git a/desktop/src-tauri/src/downloads/download_manager_builder.rs b/desktop/src-tauri/src/downloads/download_manager_builder.rs index fa3e13ff..1c4d53fc 100644 --- a/desktop/src-tauri/src/downloads/download_manager_builder.rs +++ b/desktop/src-tauri/src/downloads/download_manager_builder.rs @@ -4,7 +4,7 @@ use std::{ mpsc::{channel, Receiver, Sender}, Arc, Mutex, }, - thread::spawn, + thread::{spawn, JoinHandle}, }; use log::{error, info}; @@ -77,6 +77,7 @@ pub struct DownloadManagerBuilder { app_handle: AppHandle, current_download_agent: Option>, // Should be the only game download agent in the map with the "Go" flag + current_download_thread: Mutex>>, active_control_flag: Option, } @@ -91,12 +92,14 @@ impl DownloadManagerBuilder { download_agent_registry: HashMap::new(), download_queue: queue.clone(), command_receiver, - current_download_agent: None, - active_control_flag: None, status: status.clone(), sender: command_sender.clone(), progress: active_progress.clone(), app_handle, + + current_download_agent: None, + current_download_thread: Mutex::new(None), + active_control_flag: None, }; let terminator = spawn(|| manager.manage_queue()); @@ -138,14 +141,23 @@ impl DownloadManagerBuilder { self.app_handle.emit("update_queue", event_data).unwrap(); } - fn cleanup_current_game(&mut self, game_id: &String) -> Arc { + fn remove_and_cleanup_game(&mut self, game_id: &String) -> Arc { self.download_queue.pop_front(); let download_agent = self.download_agent_registry.remove(game_id).unwrap(); + self.cleanup_current_download(); + return download_agent; + } + + // CAREFUL WITH THIS FUNCTION + // Make sure the download thread is terminated + fn cleanup_current_download(&mut self) { self.active_control_flag = None; *self.progress.lock().unwrap() = None; self.current_download_agent = None; - return download_agent; + let mut download_thread_lock = self.current_download_thread.lock().unwrap(); + *download_thread_lock = None; + drop(download_thread_lock); } fn manage_queue(mut self) -> Result<(), ()> { @@ -177,8 +189,8 @@ impl DownloadManagerBuilder { DownloadManagerSignal::Error(e) => { self.manage_error_signal(e); } - DownloadManagerSignal::Cancel(id) => { - self.manage_cancel_signal(id); + DownloadManagerSignal::Cancel => { + self.manage_cancel_signal(); } DownloadManagerSignal::Update => { self.push_manager_update(); @@ -200,7 +212,7 @@ impl DownloadManagerBuilder { // When if let chains are stabilised, combine these two statements if interface.id == game_id { info!("Popping consumed data"); - let download_agent = self.cleanup_current_game(&game_id); + let download_agent = self.remove_and_cleanup_game(&game_id); if let Err(error) = on_game_complete(game_id, download_agent.version.clone(), &self.app_handle) @@ -241,8 +253,6 @@ impl DownloadManagerBuilder { } fn manage_go_signal(&mut self) { - info!("Got signal 'Go'"); - if !(!self.download_agent_registry.is_empty() && !self.download_queue.empty()) { return; } @@ -252,8 +262,9 @@ impl DownloadManagerBuilder { return; } - info!("Starting download agent"); + info!("current download queue: {:?}", self.download_queue.read()); let agent_data = self.download_queue.read().front().unwrap().clone(); + info!("starting download for {}", agent_data.id.clone()); let download_agent = self .download_agent_registry .get(&agent_data.id) @@ -274,7 +285,8 @@ impl DownloadManagerBuilder { let sender = self.sender.clone(); info!("Spawning download"); - spawn(move || { + let mut download_thread_lock = self.current_download_thread.lock().unwrap(); + *download_thread_lock = Some(spawn(move || { match download_agent.download() { // Returns once we've exited the download // (not necessarily completed) @@ -286,7 +298,7 @@ impl DownloadManagerBuilder { sender.send(DownloadManagerSignal::Error(err)).unwrap(); } }; - }); + })); // Set status for game let mut status_handle = agent_data.status.lock().unwrap(); @@ -303,7 +315,7 @@ impl DownloadManagerBuilder { fn manage_error_signal(&mut self, error: GameDownloadError) { let current_status = self.current_download_agent.clone().unwrap(); - self.cleanup_current_game(¤t_status.id); // Remove all the locks and shit + self.remove_and_cleanup_game(¤t_status.id); // Remove all the locks and shit let mut lock = current_status.status.lock().unwrap(); *lock = GameDownloadStatus::Error; @@ -314,33 +326,20 @@ impl DownloadManagerBuilder { self.sender.send(DownloadManagerSignal::Update).unwrap(); } - fn manage_cancel_signal(&mut self, game_id: String) { + fn manage_cancel_signal(&mut self) { if let Some(current_flag) = &self.active_control_flag { current_flag.set(DownloadThreadControlFlag::Stop); } - // TODO wait until current download exits - // This cleanup function might break things because it - // unsets the control flag - self.cleanup_current_game(&game_id); + let mut download_thread_lock = self.current_download_thread.lock().unwrap(); + if let Some(current_download_thread) = download_thread_lock.take() { + current_download_thread.join().unwrap(); + } + drop(download_thread_lock); - self.download_agent_registry.remove(&game_id); - let mut lock = self.download_queue.edit(); - let index = match lock.iter().position(|interface| interface.id == game_id) { - Some(index) => index, - None => return, - }; - lock.remove(index); + info!("cancel waited for download to finish"); - // Start next download - self.sender.send(DownloadManagerSignal::Go).unwrap(); - info!( - "{:?}", - self.download_agent_registry - .iter() - .map(|x| x.0.clone()) - .collect::() - ); + self.cleanup_current_download(); } fn set_status(&self, status: DownloadManagerStatus) { *self.status.lock().unwrap() = status; diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs index 87850072..31c51f6d 100644 --- a/desktop/src-tauri/src/lib.rs +++ b/desktop/src-tauri/src/lib.rs @@ -135,7 +135,6 @@ pub fn run() { // Downloads download_game, move_game_in_queue, - cancel_game_download, pause_game_downloads, resume_game_downloads, ]) From 3e45d15f10a0bc9bc4b0a7de933f796fd8ed8bfe Mon Sep 17 00:00:00 2001 From: Louis van Liefland <116044207+quexeky@users.noreply.github.com> Date: Mon, 9 Dec 2024 20:32:42 +1100 Subject: [PATCH 30/31] refactor: Ran cargo clippy & cargo fmt --- desktop/src-tauri/src/auth.rs | 6 ++-- desktop/src-tauri/src/db.rs | 8 ++--- .../src-tauri/src/downloads/download_agent.rs | 11 ++++--- .../src/downloads/download_commands.rs | 4 +-- .../src-tauri/src/downloads/download_logic.rs | 10 +++---- .../src/downloads/download_manager.rs | 1 + .../src/downloads/download_manager_builder.rs | 4 +-- desktop/src-tauri/src/downloads/manifest.rs | 5 ++-- desktop/src-tauri/src/downloads/mod.rs | 4 +-- .../src/downloads/progress_object.rs | 2 +- desktop/src-tauri/src/downloads/queue.rs | 8 +++-- desktop/src-tauri/src/lib.rs | 4 +-- desktop/src-tauri/src/library.rs | 30 +++++++------------ desktop/src-tauri/src/remote.rs | 12 ++++---- 14 files changed, 52 insertions(+), 57 deletions(-) diff --git a/desktop/src-tauri/src/auth.rs b/desktop/src-tauri/src/auth.rs index 6761cecc..5cb6aaf1 100644 --- a/desktop/src-tauri/src/auth.rs +++ b/desktop/src-tauri/src/auth.rs @@ -1,5 +1,7 @@ use std::{ - borrow::BorrowMut, env, sync::Mutex, time::{SystemTime, UNIX_EPOCH} + env, + sync::Mutex, + time::{SystemTime, UNIX_EPOCH}, }; use log::{info, warn}; @@ -9,7 +11,7 @@ use tauri::{AppHandle, Emitter, Manager}; use url::Url; use crate::{ - db::{self, DatabaseAuth, DatabaseImpls}, + db::{DatabaseAuth, DatabaseImpls}, remote::RemoteAccessError, AppState, AppStatus, User, DB, }; diff --git a/desktop/src-tauri/src/db.rs b/desktop/src-tauri/src/db.rs index d4ba64df..d9122e62 100644 --- a/desktop/src-tauri/src/db.rs +++ b/desktop/src-tauri/src/db.rs @@ -10,7 +10,6 @@ use log::debug; use rustbreak::{DeSerError, DeSerializer, PathDatabase}; use rustix::path::Arg; use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use serde_json::json; use url::Url; use crate::DB; @@ -100,7 +99,8 @@ impl DatabaseImpls for DatabaseInterface { #[allow(clippy::let_and_return)] let exists = fs::exists(db_path.clone()).unwrap(); - let db = match exists { + + match exists { true => PathDatabase::load_from_path(db_path).expect("Database loading failed"), false => { let default = Database { @@ -116,9 +116,7 @@ impl DatabaseImpls for DatabaseInterface { PathDatabase::create_at_path(db_path, default) .expect("Database could not be created") } - }; - - db + } } fn database_is_set_up(&self) -> bool { diff --git a/desktop/src-tauri/src/downloads/download_agent.rs b/desktop/src-tauri/src/downloads/download_agent.rs index a5b2d7de..380797fa 100644 --- a/desktop/src-tauri/src/downloads/download_agent.rs +++ b/desktop/src-tauri/src/downloads/download_agent.rs @@ -179,7 +179,7 @@ impl GameDownloadAgent { drop(context_lock); self.generate_contexts()?; - return Ok(()); + Ok(()) } pub fn generate_contexts(&self) -> Result<(), GameDownloadError> { @@ -208,7 +208,7 @@ impl GameDownloadAgent { for (i, length) in chunk.lengths.iter().enumerate() { contexts.push(DropDownloadContext { file_name: raw_path.to_string(), - version: chunk.versionName.to_string(), + version: chunk.version_name.to_string(), offset: running_offset, index: i, game_id: game_id.to_string(), @@ -257,13 +257,12 @@ impl GameDownloadAgent { scope.spawn(move |_| { match download_game_chunk(context.clone(), control_flag, progress_handle) { - Ok(res) => match res { - true => { + Ok(res) => { + if res { let mut lock = completed_indexes_ref.lock().unwrap(); lock.push(index); } - false => {} - }, + } Err(e) => { error!("GameDownloadError: {}", e); self.sender.send(DownloadManagerSignal::Error(e)).unwrap(); diff --git a/desktop/src-tauri/src/downloads/download_commands.rs b/desktop/src-tauri/src/downloads/download_commands.rs index 1bf1dee6..23b8bab8 100644 --- a/desktop/src-tauri/src/downloads/download_commands.rs +++ b/desktop/src-tauri/src/downloads/download_commands.rs @@ -1,7 +1,5 @@ use std::sync::Mutex; -use log::info; - use crate::AppState; #[tauri::command] @@ -42,8 +40,10 @@ pub fn move_game_in_queue( .rearrange(old_index, new_index) } +/* #[tauri::command] pub fn get_current_write_speed(state: tauri::State<'_, Mutex>) {} +*/ /* fn use_download_agent( diff --git a/desktop/src-tauri/src/downloads/download_logic.rs b/desktop/src-tauri/src/downloads/download_logic.rs index bdb3c644..6735c56b 100644 --- a/desktop/src-tauri/src/downloads/download_logic.rs +++ b/desktop/src-tauri/src/downloads/download_logic.rs @@ -3,23 +3,21 @@ use crate::db::DatabaseImpls; use crate::downloads::manifest::DropDownloadContext; use crate::remote::RemoteAccessError; use crate::DB; -use log::{info, warn}; +use log::warn; use md5::{Context, Digest}; use reqwest::blocking::Response; use std::io::Read; -use std::sync::atomic::{AtomicUsize, Ordering}; use std::{ fs::{File, OpenOptions}, - io::{self, BufWriter, ErrorKind, Seek, SeekFrom, Write}, + io::{self, BufWriter, Seek, SeekFrom, Write}, path::PathBuf, - sync::Arc, }; use urlencoding::encode; use super::download_agent::GameDownloadError; use super::download_thread_control_flag::{DownloadThreadControl, DownloadThreadControlFlag}; -use super::progress_object::{ProgressHandle, ProgressObject}; +use super::progress_object::ProgressHandle; pub struct DropWriter { hasher: Context, @@ -182,7 +180,7 @@ pub fn download_game_chunk( content_length.unwrap().try_into().unwrap(), ); - let completed = pipeline.copy().map_err(|e| GameDownloadError::IoError(e))?; + let completed = pipeline.copy().map_err(GameDownloadError::IoError)?; if !completed { return Ok(false); }; diff --git a/desktop/src-tauri/src/downloads/download_manager.rs b/desktop/src-tauri/src/downloads/download_manager.rs index 9f8799b1..f04135fa 100644 --- a/desktop/src-tauri/src/downloads/download_manager.rs +++ b/desktop/src-tauri/src/downloads/download_manager.rs @@ -91,6 +91,7 @@ impl Debug for GameDownloadAgentQueueStandin { } } +#[allow(dead_code)] impl DownloadManager { pub fn new( terminator: JoinHandle>, diff --git a/desktop/src-tauri/src/downloads/download_manager_builder.rs b/desktop/src-tauri/src/downloads/download_manager_builder.rs index 1c4d53fc..1b338533 100644 --- a/desktop/src-tauri/src/downloads/download_manager_builder.rs +++ b/desktop/src-tauri/src/downloads/download_manager_builder.rs @@ -120,7 +120,7 @@ impl DownloadManagerBuilder { &format!("update_game/{}", id), GameUpdateEvent { game_id: id, - status: status, + status, }, ) .unwrap(); @@ -145,7 +145,7 @@ impl DownloadManagerBuilder { self.download_queue.pop_front(); let download_agent = self.download_agent_registry.remove(game_id).unwrap(); self.cleanup_current_download(); - return download_agent; + download_agent } // CAREFUL WITH THIS FUNCTION diff --git a/desktop/src-tauri/src/downloads/manifest.rs b/desktop/src-tauri/src/downloads/manifest.rs index d6cf8ec8..d8158614 100644 --- a/desktop/src-tauri/src/downloads/manifest.rs +++ b/desktop/src-tauri/src/downloads/manifest.rs @@ -4,12 +4,13 @@ use std::path::PathBuf; pub type DropManifest = HashMap; #[derive(Serialize, Deserialize, Debug, Clone, Ord, PartialOrd, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct DropChunk { pub permissions: usize, pub ids: Vec, pub checksums: Vec, pub lengths: Vec, - pub versionName: String, + pub version_name: String, } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -21,5 +22,5 @@ pub struct DropDownloadContext { pub game_id: String, pub path: PathBuf, pub checksum: String, - pub length: usize + pub length: usize, } diff --git a/desktop/src-tauri/src/downloads/mod.rs b/desktop/src-tauri/src/downloads/mod.rs index c7c11eea..0102c33e 100644 --- a/desktop/src-tauri/src/downloads/mod.rs +++ b/desktop/src-tauri/src/downloads/mod.rs @@ -1,9 +1,9 @@ pub mod download_agent; pub mod download_commands; mod download_logic; -pub mod download_manager_builder; pub mod download_manager; +pub mod download_manager_builder; mod download_thread_control_flag; mod manifest; mod progress_object; -pub mod queue; \ No newline at end of file +pub mod queue; diff --git a/desktop/src-tauri/src/downloads/progress_object.rs b/desktop/src-tauri/src/downloads/progress_object.rs index fac9b9b0..143656dd 100644 --- a/desktop/src-tauri/src/downloads/progress_object.rs +++ b/desktop/src-tauri/src/downloads/progress_object.rs @@ -68,7 +68,7 @@ impl ProgressObject { .fetch_add(amount_added, Ordering::Relaxed); let to_update_handle = self.points_to_push_update.lock().unwrap(); - let to_update = to_update_handle.clone(); + let to_update = *to_update_handle; drop(to_update_handle); if current_amount < to_update { diff --git a/desktop/src-tauri/src/downloads/queue.rs b/desktop/src-tauri/src/downloads/queue.rs index f4139aca..0ea65cae 100644 --- a/desktop/src-tauri/src/downloads/queue.rs +++ b/desktop/src-tauri/src/downloads/queue.rs @@ -10,6 +10,7 @@ pub struct Queue { inner: Arc>>>, } +#[allow(dead_code)] impl Queue { pub fn new() -> Self { Self { @@ -40,7 +41,10 @@ impl Queue { pub fn append(&self, interface: GameDownloadAgentQueueStandin) { self.edit().push_back(Arc::new(interface)); } - pub fn pop_front_if_equal(&self, game_id: String) -> Option> { + pub fn pop_front_if_equal( + &self, + game_id: String, + ) -> Option> { let mut queue = self.edit(); let front = match queue.front() { Some(front) => front, @@ -49,7 +53,7 @@ impl Queue { if front.id == game_id { return queue.pop_front(); } - return None; + None } pub fn get_by_id(&self, game_id: String) -> Option { self.read().iter().position(|data| data.id == game_id) diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs index 31c51f6d..db0d3b35 100644 --- a/desktop/src-tauri/src/lib.rs +++ b/desktop/src-tauri/src/lib.rs @@ -2,7 +2,7 @@ mod auth; mod db; mod downloads; mod library; -mod p2p; +// mod p2p; mod remote; mod settings; #[cfg(test)] @@ -21,7 +21,7 @@ use env_logger::Env; use http::{header::*, response::Builder as ResponseBuilder}; use library::{fetch_game, fetch_game_status, fetch_game_verion_options, fetch_library, Game}; use log::{debug, info}; -use remote::{gen_drop_url, use_remote, RemoteAccessError}; +use remote::{gen_drop_url, use_remote}; use serde::{Deserialize, Serialize}; use std::sync::Arc; use std::{ diff --git a/desktop/src-tauri/src/library.rs b/desktop/src-tauri/src/library.rs index 139b04c7..0e25c526 100644 --- a/desktop/src-tauri/src/library.rs +++ b/desktop/src-tauri/src/library.rs @@ -1,17 +1,13 @@ -use std::collections::{HashMap, VecDeque}; -use std::fmt::format; use std::sync::Mutex; -use log::info; use serde::{Deserialize, Serialize}; -use serde_json::json; use tauri::Emitter; use tauri::{AppHandle, Manager}; use urlencoding::encode; use crate::db::DatabaseGameStatus; use crate::db::DatabaseImpls; -use crate::db::{self, GameVersion}; +use crate::db::GameVersion; use crate::downloads::download_manager::GameDownloadStatus; use crate::remote::RemoteAccessError; use crate::{auth::generate_authorization_header, AppState, DB}; @@ -159,12 +155,11 @@ fn fetch_game_logic( let mut db_handle = DB.borrow_data_mut().unwrap(); - if !db_handle.games.games_statuses.contains_key(&id) { - db_handle - .games - .games_statuses - .insert(id, DatabaseGameStatus::Remote {}); - } + db_handle + .games + .games_statuses + .entry(id) + .or_insert(DatabaseGameStatus::Remote {}); let data = FetchGameStruct { game: game.clone(), @@ -176,7 +171,7 @@ fn fetch_game_logic( .clone(), }; - return Ok(data); + Ok(data) } #[tauri::command] @@ -201,7 +196,7 @@ pub fn fetch_game_status(id: String) -> Result { .clone(); drop(db_handle); - return Ok(status); + Ok(status) } fn fetch_game_verion_options_logic( @@ -227,7 +222,7 @@ fn fetch_game_verion_options_logic( let data = response.json::>()?; - return Ok(data); + Ok(data) } #[tauri::command] @@ -266,7 +261,7 @@ pub fn on_game_complete( .games .game_versions .entry(game_id.clone()) - .or_insert(HashMap::new()) + .or_default() .insert(version_name.clone(), data.clone()); drop(handle); DB.save().unwrap(); @@ -287,10 +282,7 @@ pub fn on_game_complete( app_handle .emit( &format!("update_game/{}", game_id), - GameUpdateEvent { - game_id: game_id, - status: status, - }, + GameUpdateEvent { game_id, status }, ) .unwrap(); diff --git a/desktop/src-tauri/src/remote.rs b/desktop/src-tauri/src/remote.rs index 8e05d4ca..0a53c2b9 100644 --- a/desktop/src-tauri/src/remote.rs +++ b/desktop/src-tauri/src/remote.rs @@ -5,7 +5,7 @@ use std::{ use http::StatusCode; use log::{info, warn}; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; use url::{ParseError, Url}; use crate::{AppState, AppStatus, DB}; @@ -20,7 +20,7 @@ pub enum RemoteAccessError { GameNotFound, InvalidResponse, InvalidRedirect, - ManifestDownloadFailed(StatusCode, String) + ManifestDownloadFailed(StatusCode, String), } impl Display for RemoteAccessError { @@ -36,10 +36,10 @@ impl Display for RemoteAccessError { RemoteAccessError::GameNotFound => write!(f, "Could not find game on server"), RemoteAccessError::InvalidResponse => write!(f, "Server returned an invalid response"), RemoteAccessError::InvalidRedirect => write!(f, "Server redirect was invalid"), - RemoteAccessError::ManifestDownloadFailed(status, response) => - write!(f, "Failed to download game manifest: {} {}", - status, - response + RemoteAccessError::ManifestDownloadFailed(status, response) => write!( + f, + "Failed to download game manifest: {} {}", + status, response ), } } From 10d998cd298be623a04c9ce695aa5cbe8d062406 Mon Sep 17 00:00:00 2001 From: DecDuck Date: Mon, 9 Dec 2024 20:41:36 +1100 Subject: [PATCH 31/31] fix: windows build --- desktop/src-tauri/src/db.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/desktop/src-tauri/src/db.rs b/desktop/src-tauri/src/db.rs index d9122e62..38b11fcf 100644 --- a/desktop/src-tauri/src/db.rs +++ b/desktop/src-tauri/src/db.rs @@ -8,7 +8,6 @@ use std::{ use directories::BaseDirs; use log::debug; use rustbreak::{DeSerError, DeSerializer, PathDatabase}; -use rustix::path::Arg; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use url::Url; @@ -112,7 +111,7 @@ impl DatabaseImpls for DatabaseInterface { game_versions: HashMap::new(), }, }; - debug!("Creating database at path {}", db_path.as_str().unwrap()); + debug!("Creating database at path {}", db_path.as_os_str().to_str().unwrap()); PathDatabase::create_at_path(db_path, default) .expect("Database could not be created") }