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

View File

@ -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);

View File

@ -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",

View File

@ -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) {}

View File

@ -27,8 +27,9 @@ pub struct DropWriter<W: Write> {
}
impl DropWriter<File> {
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(),
}
}

View File

@ -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<Vec<(String, bool)>>,
pub contexts: Mutex<HashMap<String, bool>>,
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<String> {
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<String, bool> {
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()
}

View File

@ -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