Fix for redownload invalid chunks (#84)

* feat: Redownloading invalid chunks

Signed-off-by: quexeky <git@quexeky.dev>

* fix: clippy

* fix: clippy x2

---------

Signed-off-by: quexeky <git@quexeky.dev>
Co-authored-by: quexeky <git@quexeky.dev>
This commit is contained in:
DecDuck
2025-07-31 18:25:38 +10:00
committed by GitHub
parent d0e4aea5ce
commit b5a8543194
7 changed files with 66 additions and 54 deletions
@@ -242,43 +242,47 @@ impl DownloadManagerBuilder {
let app_handle = self.app_handle.clone(); let app_handle = self.app_handle.clone();
*download_thread_lock = Some(spawn(move || { *download_thread_lock = Some(spawn(move || {
match download_agent.download(&app_handle) { loop {
// Ok(true) is for completed and exited properly let download_result = match download_agent.download(&app_handle) {
Ok(true) => { // Ok(true) is for completed and exited properly
debug!("download {:?} has completed", download_agent.metadata()); Ok(v) => v,
match download_agent.validate() { Err(e) => {
Ok(true) => { error!("download {:?} has error {}", download_agent.metadata(), &e);
download_agent.on_complete(&app_handle); download_agent.on_error(&app_handle, &e);
sender sender.send(DownloadManagerSignal::Error(e)).unwrap();
.send(DownloadManagerSignal::Completed(download_agent.metadata())) return;
.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();
}
} }
} };
// Ok(false) is for incomplete but exited properly
Ok(false) => { // If the download gets cancel
debug!("Donwload agent finished incomplete"); if !download_result {
download_agent.on_incomplete(&app_handle); download_agent.on_incomplete(&app_handle);
return;
} }
Err(e) => {
error!("download {:?} has error {}", download_agent.metadata(), &e); let validate_result = match download_agent.validate() {
download_agent.on_error(&app_handle, &e); Ok(v) => v,
sender.send(DownloadManagerSignal::Error(e)).unwrap(); 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); self.set_status(DownloadManagerStatus::Downloading);
-2
View File
@@ -4,7 +4,6 @@ use serde_with::SerializeDisplay;
#[derive(SerializeDisplay)] #[derive(SerializeDisplay)]
pub enum ProcessError { pub enum ProcessError {
SetupRequired,
NotInstalled, NotInstalled,
AlreadyRunning, AlreadyRunning,
NotDownloaded, NotDownloaded,
@@ -19,7 +18,6 @@ pub enum ProcessError {
impl Display for ProcessError { impl Display for ProcessError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self { let s = match self {
ProcessError::SetupRequired => "Game not set up",
ProcessError::NotInstalled => "Game not installed", ProcessError::NotInstalled => "Game not installed",
ProcessError::AlreadyRunning => "Game already running", ProcessError::AlreadyRunning => "Game already running",
ProcessError::NotDownloaded => "Game not downloaded", ProcessError::NotDownloaded => "Game not downloaded",
@@ -18,7 +18,7 @@ use crate::remote::requests::make_request;
use log::{debug, error, info}; use log::{debug, error, info};
use rayon::ThreadPoolBuilder; use rayon::ThreadPoolBuilder;
use std::collections::HashMap; 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::path::{Path, PathBuf};
use std::sync::mpsc::Sender; use std::sync::mpsc::Sender;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@@ -26,7 +26,7 @@ use std::time::Instant;
use tauri::{AppHandle, Emitter}; use tauri::{AppHandle, Emitter};
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use rustix::fs::{fallocate, FallocateFlags}; use rustix::fs::{FallocateFlags, fallocate};
use super::download_logic::download_game_chunk; use super::download_logic::download_game_chunk;
use super::drop_data::DropData; use super::drop_data::DropData;
@@ -187,10 +187,7 @@ impl GameDownloadAgent {
self.generate_contexts()?; self.generate_contexts()?;
} }
self.context_map *self.context_map.lock().unwrap() = self.stored_manifest.get_contexts();
.lock()
.unwrap()
.extend(self.stored_manifest.get_contexts());
Ok(()) Ok(())
} }
@@ -423,6 +420,7 @@ impl Downloadable for GameDownloadAgent {
app_handle, app_handle,
) )
.unwrap(); .unwrap();
println!("Attempting to redownload");
} }
fn on_cancelled(&self, _app_handle: &tauri::AppHandle) {} fn on_cancelled(&self, _app_handle: &tauri::AppHandle) {}
@@ -27,8 +27,9 @@ pub struct DropWriter<W: Write> {
} }
impl DropWriter<File> { impl DropWriter<File> {
fn new(path: PathBuf) -> Self { fn new(path: PathBuf) -> Self {
let destination = OpenOptions::new().write(true).create(true).truncate(false).open(&path).unwrap();
Self { Self {
destination: OpenOptions::new().write(true).open(path).unwrap(), destination,
hasher: Context::new(), hasher: Context::new(),
} }
} }
+11 -10
View File
@@ -1,7 +1,5 @@
use std::{ use std::{
fs::File, collections::HashMap, fs::File, io::{Read, Write}, path::PathBuf
io::{Read, Write},
path::PathBuf,
}; };
use log::{debug, error, info, warn}; use log::{debug, error, info, warn};
@@ -12,7 +10,7 @@ pub type DropData = v1::DropData;
static DROP_DATA_PATH: &str = ".dropdata"; static DROP_DATA_PATH: &str = ".dropdata";
pub mod v1 { pub mod v1 {
use std::{path::PathBuf, sync::Mutex}; use std::{collections::HashMap, path::PathBuf, sync::Mutex};
use native_model::native_model; use native_model::native_model;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -22,7 +20,7 @@ pub mod v1 {
pub struct DropData { pub struct DropData {
pub game_id: String, pub game_id: String,
pub game_version: String, pub game_version: String,
pub contexts: Mutex<Vec<(String, bool)>>, pub contexts: Mutex<HashMap<String, bool>>,
pub base_path: PathBuf, pub base_path: PathBuf,
} }
@@ -32,7 +30,7 @@ pub mod v1 {
base_path, base_path,
game_id, game_id,
game_version, 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)]) { 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<String> { pub fn get_completed_contexts(&self) -> Vec<String> {
self.contexts self.contexts
.lock() .lock()
.unwrap() .unwrap()
.iter() .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() .collect()
} }
pub fn get_contexts(&self) -> Vec<(String, bool)> { pub fn get_contexts(&self) -> HashMap<String, bool> {
info!( info!(
"Any contexts which are complete? {}", "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() self.contexts.lock().unwrap().clone()
} }
+12 -2
View File
@@ -1,7 +1,7 @@
use std::{ use std::{
fs::File, fs::File,
io::{self, BufWriter, Read, Seek, SeekFrom, Write}, io::{self, BufWriter, Read, Seek, SeekFrom, Write},
sync::{mpsc::Sender, Arc}, sync::{Arc, mpsc::Sender},
}; };
use log::{debug, error, info}; use log::{debug, error, info};
@@ -73,6 +73,7 @@ pub fn game_validate_logic(
} }
}); });
// If there are any contexts left which are false // If there are any contexts left which are false
if !invalid_chunks.is_empty() { if !invalid_chunks.is_empty() {
info!( info!(
@@ -80,6 +81,13 @@ pub fn game_validate_logic(
dropdata.game_id.clone(), dropdata.game_id.clone(),
invalid_chunks invalid_chunks
); );
for context in invalid_chunks.iter() {
dropdata.set_context(context.1.clone(), false);
}
dropdata.write();
return Ok(false); return Ok(false);
} }
@@ -101,7 +109,9 @@ pub fn validate_game_chunk(
return Ok(false); 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 { if ctx.offset != 0 {
source source