From 37114bc2f66388fc514dc7ff045f5d33d1fe035f Mon Sep 17 00:00:00 2001 From: Observer KRypt0n_ Date: Wed, 15 Nov 2023 13:09:17 +0200 Subject: [PATCH] feat: added repair game feature --- src/ui/main/mod.rs | 4 + src/ui/main/repair_game.rs | 137 ++++++++++++++++++++++++++++++ src/ui/preferences/general/mod.rs | 11 +++ src/ui/preferences/main.rs | 8 ++ 4 files changed, 160 insertions(+) create mode 100644 src/ui/main/repair_game.rs diff --git a/src/ui/main/mod.rs b/src/ui/main/mod.rs index 16d5736..06d2d1a 100644 --- a/src/ui/main/mod.rs +++ b/src/ui/main/mod.rs @@ -9,6 +9,7 @@ use adw::prelude::*; use gtk::glib::clone; +mod repair_game; mod update_patch; mod download_wine; mod create_prefix; @@ -92,6 +93,7 @@ pub enum AppMsg { DisableKillGameButton(bool), OpenPreferences, + RepairGame, PredownloadUpdate, PerformAction, @@ -1109,6 +1111,8 @@ impl SimpleComponent for App { PREFERENCES_WINDOW.as_ref().unwrap_unchecked().widget().present(); } + AppMsg::RepairGame => repair_game::repair_game(sender, self.progress_bar.sender().to_owned()), + #[allow(unused_must_use)] AppMsg::PredownloadUpdate => { if let Some(LauncherState::PredownloadAvailable { game, mut voices, .. }) = self.state.clone() { diff --git a/src/ui/main/repair_game.rs b/src/ui/main/repair_game.rs new file mode 100644 index 0000000..c3db008 --- /dev/null +++ b/src/ui/main/repair_game.rs @@ -0,0 +1,137 @@ +use relm4::{ + prelude::*, + Sender +}; + +use gtk::glib::clone; + +use crate::*; +use crate::ui::components::*; + +use super::{App, AppMsg}; + +#[allow(unused_must_use)] +pub fn repair_game(sender: ComponentSender, progress_bar_input: Sender) { + let config = Config::get().unwrap(); + + progress_bar_input.send(ProgressBarMsg::UpdateCaption(Some(tr!("verifying-files")))); + sender.input(AppMsg::SetDownloading(true)); + + std::thread::spawn(move || { + match repairer::try_get_integrity_files(config.launcher.edition, None) { + Ok(mut files) => { + // Add voiceovers files + let game_path = config.game.path.for_edition(config.launcher.edition).to_path_buf(); + let game = Game::new(&game_path, config.launcher.edition); + + if let Ok(voiceovers) = game.get_voice_packages() { + for package in voiceovers { + if let Ok(mut voiceover_files) = repairer::try_get_voice_integrity_files(config.launcher.edition, package.locale(), None) { + files.append(&mut voiceover_files); + } + } + } + + progress_bar_input.send(ProgressBarMsg::UpdateProgress(0, 0)); + + let mut total = 0; + + for file in &files { + total += file.size; + } + + let median_size = total / config.launcher.repairer.threads; + let mut i = 0; + + let (verify_sender, verify_receiver) = std::sync::mpsc::channel(); + + for _ in 0..config.launcher.repairer.threads { + let mut thread_files = Vec::new(); + let mut thread_files_size = 0; + + while i < files.len() { + thread_files.push(files[i].clone()); + + thread_files_size += files[i].size; + i += 1; + + if thread_files_size >= median_size { + break; + } + } + + let thread_sender = verify_sender.clone(); + + std::thread::spawn(clone!(@strong game_path => move || { + for file in thread_files { + let status = if config.launcher.repairer.fast { + file.fast_verify(&game_path) + } else { + file.verify(&game_path) + }; + + thread_sender.send((file, status)).unwrap(); + } + })); + } + + // We have [config.launcher.repairer.threads] copies of this sender + the original one + // receiver will return Err when all the senders will be dropped. + // [config.launcher.repairer.threads] senders will be dropped when threads will finish verifying files + // but this one will live as long as current thread exists so we should drop it manually + drop(verify_sender); + + let mut broken = Vec::new(); + let mut processed = 0; + + while let Ok((file, status)) = verify_receiver.recv() { + processed += file.size; + + if !status { + broken.push(file); + } + + progress_bar_input.send(ProgressBarMsg::UpdateProgress(processed, total)); + } + + if !broken.is_empty() { + let total = broken.len() as u64; + + progress_bar_input.send(ProgressBarMsg::UpdateCaption(Some(tr!("repairing-files")))); + progress_bar_input.send(ProgressBarMsg::DisplayFraction(false)); + progress_bar_input.send(ProgressBarMsg::UpdateProgress(0, total)); + + tracing::warn!("Found broken files:\n{}", broken.iter().fold(String::new(), |acc, file| acc + &format!("- {}\n", file.path.to_string_lossy()))); + + for (i, file) in broken.into_iter().enumerate() { + tracing::debug!("Repairing file: {}", file.path.to_string_lossy()); + + if let Err(err) = file.repair(&game_path) { + sender.input(AppMsg::Toast { + title: tr!("game-file-repairing-error"), + description: Some(err.to_string()) + }); + + tracing::error!("Failed to repair game file: {err}"); + } + + progress_bar_input.send(ProgressBarMsg::UpdateProgress(i as u64 + 1, total)); + } + + progress_bar_input.send(ProgressBarMsg::DisplayFraction(true)); + } + } + + Err(err) => { + tracing::error!("Failed to get inregrity failes: {err}"); + + sender.input(AppMsg::Toast { + title: tr!("integrity-files-getting-error"), + description: Some(err.to_string()) + }); + } + } + + sender.input(AppMsg::SetDownloading(false)); + }); +} diff --git a/src/ui/preferences/general/mod.rs b/src/ui/preferences/general/mod.rs index 09a60b3..1b42876 100644 --- a/src/ui/preferences/general/mod.rs +++ b/src/ui/preferences/general/mod.rs @@ -134,6 +134,7 @@ pub enum GeneralAppMsg { UpdateDownloadedDxvk, OpenMigrateInstallation, + RepairGame, OpenMainPage, OpenComponentsPage, @@ -319,6 +320,12 @@ impl SimpleAsyncComponent for GeneralApp { set_tooltip_text: Some(&tr!("migrate-installation-description")), connect_clicked => GeneralAppMsg::OpenMigrateInstallation + }, + + gtk::Button { + set_label: &tr!("repair-game"), + + connect_clicked => GeneralAppMsg::RepairGame } } }, @@ -669,6 +676,10 @@ impl SimpleAsyncComponent for GeneralApp { self.migrate_installation.widget().present(); } + GeneralAppMsg::RepairGame => { + sender.output(Self::Output::RepairGame).unwrap(); + } + GeneralAppMsg::OpenMainPage => unsafe { PREFERENCES_WINDOW.as_ref() .unwrap_unchecked() diff --git a/src/ui/preferences/main.rs b/src/ui/preferences/main.rs index deb8dd7..2ea0335 100644 --- a/src/ui/preferences/main.rs +++ b/src/ui/preferences/main.rs @@ -35,6 +35,7 @@ pub enum PreferencesAppMsg { SetLauncherStyle(LauncherStyle), UpdateLauncherState, + RepairGame, Toast { title: String, @@ -132,6 +133,13 @@ impl SimpleAsyncComponent for PreferencesApp { }); } + #[allow(unused_must_use)] + PreferencesAppMsg::RepairGame => unsafe { + PREFERENCES_WINDOW.as_ref().unwrap_unchecked().close(); + + sender.output(Self::Output::RepairGame); + } + PreferencesAppMsg::Toast { title, description } => unsafe { let toast = adw::Toast::new(&title);