chore(downloads): progress on more precise download control

Signed-off-by: quexeky <git@quexeky.dev>
This commit is contained in:
quexeky
2024-11-19 13:02:28 +11:00
parent f0c47d87fb
commit 18b914918c
5 changed files with 72 additions and 19 deletions

View File

@ -7,14 +7,17 @@ use log::info;
use rayon::ThreadPoolBuilder; use rayon::ThreadPoolBuilder;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use std::fs::{create_dir_all, File}; use std::fs::{create_dir_all, File};
use std::io;
use std::path::Path; use std::path::Path;
use std::sync::Mutex; use std::sync::mpsc::Sender;
use std::sync::{Arc, Mutex};
use urlencoding::encode; use urlencoding::encode;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use rustix::fs::{fallocate, FallocateFlags}; use rustix::fs::{fallocate, FallocateFlags};
use super::download_logic::download_game_chunk; use super::download_logic::download_game_chunk;
use super::download_manager::DownloadManagerSignal;
use super::download_thread_control_flag::{DownloadThreadControl, DownloadThreadControlFlag}; use super::download_thread_control_flag::{DownloadThreadControl, DownloadThreadControlFlag};
use super::progress_object::ProgressObject; use super::progress_object::ProgressObject;
@ -26,6 +29,7 @@ pub struct GameDownloadAgent {
contexts: Mutex<Vec<DropDownloadContext>>, contexts: Mutex<Vec<DropDownloadContext>>,
pub manifest: Mutex<Option<DropManifest>>, pub manifest: Mutex<Option<DropManifest>>,
pub progress: ProgressObject, pub progress: ProgressObject,
sender: Sender<DownloadManagerSignal>
} }
#[derive(Debug)] #[derive(Debug)]
@ -34,6 +38,8 @@ pub enum GameDownloadError {
Checksum, Checksum,
Setup(SetupError), Setup(SetupError),
Lock, Lock,
IoError(io::Error),
DownloadError
} }
#[derive(Debug)] #[derive(Debug)]
@ -48,12 +54,14 @@ impl Display for GameDownloadError {
GameDownloadError::Setup(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::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::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 { 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<DownloadManagerSignal>) -> Self {
// Don't run by default // Don't run by default
let control_flag = DownloadThreadControl::new(DownloadThreadControlFlag::Stop); let control_flag = DownloadThreadControl::new(DownloadThreadControlFlag::Stop);
Self { Self {
@ -64,11 +72,11 @@ impl GameDownloadAgent {
target_download_dir, target_download_dir,
contexts: Mutex::new(Vec::new()), contexts: Mutex::new(Vec::new()),
progress: ProgressObject::new(0, 0), progress: ProgressObject::new(0, 0),
sender
} }
} }
// Blocking // Blocking
// Requires mutable self
pub fn setup_download(&self) -> Result<(), GameDownloadError> { pub fn setup_download(&self) -> Result<(), GameDownloadError> {
self.ensure_manifest_exists()?; self.ensure_manifest_exists()?;
info!("Ensured manifest exists"); info!("Ensured manifest exists");
@ -84,7 +92,7 @@ impl GameDownloadAgent {
// Blocking // Blocking
pub fn download(&self) -> Result<(), GameDownloadError> { pub fn download(&self) -> Result<(), GameDownloadError> {
self.setup_download()?; self.setup_download()?;
self.run(); self.run().map_err(|_| GameDownloadError::DownloadError)?;
Ok(()) Ok(())
} }
@ -197,7 +205,7 @@ impl GameDownloadAgent {
Err(GameDownloadError::Setup(SetupError::Context)) Err(GameDownloadError::Setup(SetupError::Context))
} }
pub fn run(&self) { pub fn run(&self) -> Result<(), ()> {
const DOWNLOAD_MAX_THREADS: usize = 4; const DOWNLOAD_MAX_THREADS: usize = 4;
let pool = ThreadPoolBuilder::new() let pool = ThreadPoolBuilder::new()
@ -205,22 +213,48 @@ impl GameDownloadAgent {
.build() .build()
.unwrap(); .unwrap();
let new_contexts = Arc::new(Mutex::new(Vec::new()));
let new_contexts_ref = new_contexts.clone();
pool.scope(move |scope| { pool.scope(move |scope| {
let contexts = self.contexts.lock().unwrap(); let contexts = self.contexts.lock().unwrap();
for (index, context) in contexts.iter().enumerate() { for (index, context) in contexts.iter().enumerate() {
let context = context.clone(); let context = context.clone();
let control_flag = self.control_flag.clone(); // Clone arcs let control_flag = self.control_flag.clone(); // Clone arcs
let progress = self.progress.get(index); // Clone arcs let progress = self.progress.get(index); // Clone arcs
let new_contexts_ref = new_contexts_ref.clone();
scope.spawn(move |_| { scope.spawn(move |_| {
info!( info!(
"starting download for file {} {}", "starting download for file {} {}",
context.file_name, context.index 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(())
} }
} }

View File

@ -1,7 +1,5 @@
use std::sync::Mutex; use std::sync::Mutex;
use serde::Serialize;
use crate::{AppError, AppState}; use crate::{AppError, AppState};
#[tauri::command] #[tauri::command]
@ -32,7 +30,18 @@ pub fn get_current_game_download_progress(
Some(progress) => Ok(progress), Some(progress) => Ok(progress),
None => Err(AppError::DoesNotExist), None => Err(AppError::DoesNotExist),
} }
}
#[tauri::command]
pub fn stop_game_download(
state: tauri::State<'_, Mutex<AppState>>,
game_id: String
) {
state
.lock()
.unwrap()
.download_manager
.cancel_download(game_id);
} }
/* /*
fn use_download_agent( fn use_download_agent(

View File

@ -171,12 +171,12 @@ pub fn download_game_chunk(
content_length.unwrap().try_into().unwrap(), content_length.unwrap().try_into().unwrap(),
); );
let completed = pipeline.copy().unwrap(); let completed = pipeline.copy().map_err(|e| GameDownloadError::IoError(e))?;
if !completed { if !completed {
return Ok(false); return Ok(false);
}; };
let checksum = pipeline.finish().unwrap(); let checksum = pipeline.finish().map_err(|e| GameDownloadError::IoError(e))?;
let res = hex::encode(checksum.0); let res = hex::encode(checksum.0);
if res != ctx.checksum { if res != ctx.checksum {

View File

@ -91,7 +91,7 @@ pub enum GameDownloadStatus {
Downloading, Downloading,
Paused, Paused,
Uninitialised, Uninitialised,
Error(GameDownloadError), Error,
} }
impl DownloadManagerBuilder { impl DownloadManagerBuilder {
@ -143,9 +143,9 @@ impl DownloadManagerBuilder {
} }
return Ok(()); return Ok(());
} }
DownloadManagerSignal::Error(game_download_error) => { DownloadManagerSignal::Error(e) => {
self.manage_error_signal(game_download_error); self.manage_error_signal(e);
} },
}; };
} }
} }
@ -178,6 +178,7 @@ impl DownloadManagerBuilder {
id.clone(), id.clone(),
version, version,
target_download_dir, target_download_dir,
self.sender.clone()
)); ));
let agent_status = GameDownloadStatus::Uninitialised; let agent_status = GameDownloadStatus::Uninitialised;
let interface_data = Arc::new(AgentInterfaceData { let interface_data = Arc::new(AgentInterfaceData {
@ -217,11 +218,14 @@ impl DownloadManagerBuilder {
info!("Spawning download"); info!("Spawning download");
spawn(move || { spawn(move || {
let signal = match download_agent.download() { match download_agent.download() {
Ok(_) => DownloadManagerSignal::Completed(download_agent.id.clone()), Ok(_) => {
Err(e) => DownloadManagerSignal::Error(e), 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"); info!("Finished spawning Download");
@ -237,7 +241,7 @@ impl DownloadManagerBuilder {
fn manage_error_signal(&self, error: GameDownloadError) { fn manage_error_signal(&self, error: GameDownloadError) {
let current_status = self.current_game_interface.clone().unwrap(); let current_status = self.current_game_interface.clone().unwrap();
let mut lock = current_status.status.lock().unwrap(); let mut lock = current_status.status.lock().unwrap();
*lock = GameDownloadStatus::Error(error); *lock = GameDownloadStatus::Error;
self.set_status(DownloadManagerStatus::Error); self.set_status(DownloadManagerStatus::Error);
} }
fn set_status(&self, status: DownloadManagerStatus) { fn set_status(&self, status: DownloadManagerStatus) {

View File

@ -74,6 +74,12 @@ impl DownloadManager {
))?; ))?;
self.command_sender.send(DownloadManagerSignal::Go) self.command_sender.send(DownloadManagerSignal::Go)
} }
pub fn cancel_download(
&self,
id: String
) {
todo!()
}
pub fn edit(&self) -> MutexGuard<'_, VecDeque<Arc<AgentInterfaceData>>> { pub fn edit(&self) -> MutexGuard<'_, VecDeque<Arc<AgentInterfaceData>>> {
self.download_queue.lock().unwrap() self.download_queue.lock().unwrap()
} }