From b5a85431944bfb3020402af871b0eac120e2dfd0 Mon Sep 17 00:00:00 2001 From: DecDuck Date: Thu, 31 Jul 2025 18:25:38 +1000 Subject: [PATCH] Fix for redownload invalid chunks (#84) * feat: Redownloading invalid chunks Signed-off-by: quexeky * fix: clippy * fix: clippy x2 --------- Signed-off-by: quexeky Co-authored-by: quexeky --- drop-base | 2 +- .../download_manager_builder.rs | 68 ++++++++++--------- src-tauri/src/error/process_error.rs | 2 - .../src/games/downloads/download_agent.rs | 10 ++- .../src/games/downloads/download_logic.rs | 3 +- src-tauri/src/games/downloads/drop_data.rs | 21 +++--- src-tauri/src/games/downloads/validate.rs | 14 +++- 7 files changed, 66 insertions(+), 54 deletions(-) diff --git a/drop-base b/drop-base index 26698e5..04125e8 160000 --- a/drop-base +++ b/drop-base @@ -1 +1 @@ -Subproject commit 26698e5b069d463b9a02a3dd61e4888d28fa9f88 +Subproject commit 04125e89bef517411e103cdabcfa64a1bb563423 diff --git a/src-tauri/src/download_manager/download_manager_builder.rs b/src-tauri/src/download_manager/download_manager_builder.rs index c50ad20..15bd6e9 100644 --- a/src-tauri/src/download_manager/download_manager_builder.rs +++ b/src-tauri/src/download_manager/download_manager_builder.rs @@ -242,43 +242,47 @@ impl DownloadManagerBuilder { let app_handle = self.app_handle.clone(); *download_thread_lock = Some(spawn(move || { - match download_agent.download(&app_handle) { - // Ok(true) is for completed and exited properly - Ok(true) => { - debug!("download {:?} has completed", download_agent.metadata()); - match download_agent.validate() { - Ok(true) => { - download_agent.on_complete(&app_handle); - sender - .send(DownloadManagerSignal::Completed(download_agent.metadata())) - .unwrap(); - } - Ok(false) => { - download_agent.on_incomplete(&app_handle); - } - Err(e) => { - error!( - "download {:?} has validation error {}", - download_agent.metadata(), - &e - ); - download_agent.on_error(&app_handle, &e); - sender.send(DownloadManagerSignal::Error(e)).unwrap(); - } + loop { + let download_result = match download_agent.download(&app_handle) { + // Ok(true) is for completed and exited properly + Ok(v) => v, + Err(e) => { + error!("download {:?} has error {}", download_agent.metadata(), &e); + download_agent.on_error(&app_handle, &e); + sender.send(DownloadManagerSignal::Error(e)).unwrap(); + return; } - } - // Ok(false) is for incomplete but exited properly - Ok(false) => { - debug!("Donwload agent finished incomplete"); + }; + + // If the download gets cancel + if !download_result { download_agent.on_incomplete(&app_handle); + return; } - Err(e) => { - error!("download {:?} has error {}", download_agent.metadata(), &e); - download_agent.on_error(&app_handle, &e); - sender.send(DownloadManagerSignal::Error(e)).unwrap(); + + let validate_result = match download_agent.validate() { + Ok(v) => v, + Err(e) => { + error!( + "download {:?} has validation error {}", + download_agent.metadata(), + &e + ); + download_agent.on_error(&app_handle, &e); + sender.send(DownloadManagerSignal::Error(e)).unwrap(); + return; + } + }; + + if validate_result { + download_agent.on_complete(&app_handle); + sender + .send(DownloadManagerSignal::Completed(download_agent.metadata())) + .unwrap(); + sender.send(DownloadManagerSignal::UpdateUIQueue).unwrap(); + return; } } - sender.send(DownloadManagerSignal::UpdateUIQueue).unwrap(); })); self.set_status(DownloadManagerStatus::Downloading); diff --git a/src-tauri/src/error/process_error.rs b/src-tauri/src/error/process_error.rs index c04f651..0cda82f 100644 --- a/src-tauri/src/error/process_error.rs +++ b/src-tauri/src/error/process_error.rs @@ -4,7 +4,6 @@ use serde_with::SerializeDisplay; #[derive(SerializeDisplay)] pub enum ProcessError { - SetupRequired, NotInstalled, AlreadyRunning, NotDownloaded, @@ -19,7 +18,6 @@ pub enum ProcessError { impl Display for ProcessError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let s = match self { - ProcessError::SetupRequired => "Game not set up", ProcessError::NotInstalled => "Game not installed", ProcessError::AlreadyRunning => "Game already running", ProcessError::NotDownloaded => "Game not downloaded", diff --git a/src-tauri/src/games/downloads/download_agent.rs b/src-tauri/src/games/downloads/download_agent.rs index 7a452cb..9299436 100644 --- a/src-tauri/src/games/downloads/download_agent.rs +++ b/src-tauri/src/games/downloads/download_agent.rs @@ -18,7 +18,7 @@ use crate::remote::requests::make_request; use log::{debug, error, info}; use rayon::ThreadPoolBuilder; use std::collections::HashMap; -use std::fs::{create_dir_all, OpenOptions}; +use std::fs::{OpenOptions, create_dir_all}; use std::path::{Path, PathBuf}; use std::sync::mpsc::Sender; use std::sync::{Arc, Mutex}; @@ -26,7 +26,7 @@ use std::time::Instant; use tauri::{AppHandle, Emitter}; #[cfg(target_os = "linux")] -use rustix::fs::{fallocate, FallocateFlags}; +use rustix::fs::{FallocateFlags, fallocate}; use super::download_logic::download_game_chunk; use super::drop_data::DropData; @@ -187,10 +187,7 @@ impl GameDownloadAgent { self.generate_contexts()?; } - self.context_map - .lock() - .unwrap() - .extend(self.stored_manifest.get_contexts()); + *self.context_map.lock().unwrap() = self.stored_manifest.get_contexts(); Ok(()) } @@ -423,6 +420,7 @@ impl Downloadable for GameDownloadAgent { app_handle, ) .unwrap(); + println!("Attempting to redownload"); } fn on_cancelled(&self, _app_handle: &tauri::AppHandle) {} diff --git a/src-tauri/src/games/downloads/download_logic.rs b/src-tauri/src/games/downloads/download_logic.rs index 5985a62..05bd60f 100644 --- a/src-tauri/src/games/downloads/download_logic.rs +++ b/src-tauri/src/games/downloads/download_logic.rs @@ -27,8 +27,9 @@ pub struct DropWriter { } impl DropWriter { fn new(path: PathBuf) -> Self { + let destination = OpenOptions::new().write(true).create(true).truncate(false).open(&path).unwrap(); Self { - destination: OpenOptions::new().write(true).open(path).unwrap(), + destination, hasher: Context::new(), } } diff --git a/src-tauri/src/games/downloads/drop_data.rs b/src-tauri/src/games/downloads/drop_data.rs index 0408b37..4858b13 100644 --- a/src-tauri/src/games/downloads/drop_data.rs +++ b/src-tauri/src/games/downloads/drop_data.rs @@ -1,7 +1,5 @@ use std::{ - fs::File, - io::{Read, Write}, - path::PathBuf, + collections::HashMap, fs::File, io::{Read, Write}, path::PathBuf }; use log::{debug, error, info, warn}; @@ -12,7 +10,7 @@ pub type DropData = v1::DropData; static DROP_DATA_PATH: &str = ".dropdata"; pub mod v1 { - use std::{path::PathBuf, sync::Mutex}; + use std::{collections::HashMap, path::PathBuf, sync::Mutex}; use native_model::native_model; use serde::{Deserialize, Serialize}; @@ -22,7 +20,7 @@ pub mod v1 { pub struct DropData { pub game_id: String, pub game_version: String, - pub contexts: Mutex>, + pub contexts: Mutex>, pub base_path: PathBuf, } @@ -32,7 +30,7 @@ pub mod v1 { base_path, game_id, game_version, - contexts: Mutex::new(Vec::new()), + contexts: Mutex::new(HashMap::new()), } } } @@ -85,20 +83,23 @@ impl DropData { }; } pub fn set_contexts(&self, completed_contexts: &[(String, bool)]) { - *self.contexts.lock().unwrap() = completed_contexts.to_owned(); + *self.contexts.lock().unwrap() = completed_contexts.iter().map(|s| (s.0.clone(), s.1)).collect(); + } + pub fn set_context(&self, context: String, state: bool) { + self.contexts.lock().unwrap().entry(context).insert_entry(state); } pub fn get_completed_contexts(&self) -> Vec { self.contexts .lock() .unwrap() .iter() - .filter_map(|x| if x.1 { Some(x.0.clone()) } else { None }) + .filter_map(|x| if *x.1 { Some(x.0.clone()) } else { None }) .collect() } - pub fn get_contexts(&self) -> Vec<(String, bool)> { + pub fn get_contexts(&self) -> HashMap { info!( "Any contexts which are complete? {}", - self.contexts.lock().unwrap().iter().any(|x| x.1) + self.contexts.lock().unwrap().iter().any(|x| *x.1) ); self.contexts.lock().unwrap().clone() } diff --git a/src-tauri/src/games/downloads/validate.rs b/src-tauri/src/games/downloads/validate.rs index c92a106..e6d04ce 100644 --- a/src-tauri/src/games/downloads/validate.rs +++ b/src-tauri/src/games/downloads/validate.rs @@ -1,7 +1,7 @@ use std::{ fs::File, io::{self, BufWriter, Read, Seek, SeekFrom, Write}, - sync::{mpsc::Sender, Arc}, + sync::{Arc, mpsc::Sender}, }; use log::{debug, error, info}; @@ -73,6 +73,7 @@ pub fn game_validate_logic( } }); + // If there are any contexts left which are false if !invalid_chunks.is_empty() { info!( @@ -80,6 +81,13 @@ pub fn game_validate_logic( dropdata.game_id.clone(), invalid_chunks ); + + for context in invalid_chunks.iter() { + dropdata.set_context(context.1.clone(), false); + } + + dropdata.write(); + return Ok(false); } @@ -101,7 +109,9 @@ pub fn validate_game_chunk( return Ok(false); } - let mut source = File::open(&ctx.path).unwrap(); + let Ok(mut source) = File::open(&ctx.path) else { + return Ok(false); + }; if ctx.offset != 0 { source