diff --git a/Cargo.lock b/Cargo.lock index 1fed18b..dc20470 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,7 +31,7 @@ dependencies = [ [[package]] name = "anime-game-core" -version = "1.1.0" +version = "1.1.4" dependencies = [ "anyhow", "bzip2", diff --git a/anime-game-core b/anime-game-core index 6adbca8..e2140ce 160000 --- a/anime-game-core +++ b/anime-game-core @@ -1 +1 @@ -Subproject commit 6adbca88936bcb16d10063fe58688cc7b10813b2 +Subproject commit e2140cea392560ac685088d6cc6295ad1172cb72 diff --git a/assets/ui/main.blp b/assets/ui/main.blp index ad49abe..845d4a7 100644 --- a/assets/ui/main.blp +++ b/assets/ui/main.blp @@ -64,6 +64,16 @@ Adw.ApplicationWindow window { margin-top: 64; spacing: 8; + Gtk.Button predownload_game { + icon-name: "document-save-symbolic"; + tooltip-text: "Pre-download 3.1.0 update (15 GB)"; + + hexpand: false; + visible: false; + + styles ["warning"] + } + Gtk.Button launch_game { label: "Launch"; diff --git a/components b/components index 59f7158..6ae7311 160000 --- a/components +++ b/components @@ -1 +1 @@ -Subproject commit 59f7158df2b9a339fd62d4ee124f0ece47751e6b +Subproject commit 6ae731151cf1562877e5cb84896f3b5d3f001c6c diff --git a/src/lib/config/mod.rs b/src/lib/config/mod.rs index 7dc519d..4e69f61 100644 --- a/src/lib/config/mod.rs +++ b/src/lib/config/mod.rs @@ -1,7 +1,7 @@ use std::fs::File; use std::io::Read; use std::path::Path; -use std::io::{Error, ErrorKind, Write}; +use std::io::Write; use serde::{Serialize, Deserialize}; use serde_json::Value as JsonValue; @@ -39,7 +39,7 @@ static mut CONFIG: Option = None; /// This method will load config from file once and store it into the memory. /// If you know that the config file was updated - you should run `get_raw` method /// that always loads config directly from the file. This will also update in-memory config -pub fn get() -> Result { +pub fn get() -> anyhow::Result { unsafe { match &CONFIG { Some(config) => Ok(config.clone()), @@ -51,7 +51,7 @@ pub fn get() -> Result { /// Get config data /// /// This method will always load data directly from the file and update in-memory config -pub fn get_raw() -> Result { +pub fn get_raw() -> anyhow::Result { match config_file() { Some(path) => { // Try to read config if the file exists @@ -71,7 +71,7 @@ pub fn get_raw() -> Result { Ok(config) }, - Err(err) => Err(Error::new(ErrorKind::InvalidData, format!("Failed to decode data from json format: {}", err.to_string()))) + Err(err) => Err(anyhow::anyhow!("Failed to decode data from json format: {}", err.to_string())) } } @@ -82,7 +82,7 @@ pub fn get_raw() -> Result { Ok(Config::default()) } }, - None => Err(Error::new(ErrorKind::NotFound, format!("Failed to get config file path"))) + None => Err(anyhow::anyhow!("Failed to get config file path")) } } @@ -98,7 +98,7 @@ pub fn update(config: Config) { /// Update config file /// /// This method will also update in-memory config data -pub fn update_raw(config: Config) -> Result<(), Error> { +pub fn update_raw(config: Config) -> anyhow::Result<()> { update(config.clone()); match config_file() { @@ -111,19 +111,19 @@ pub fn update_raw(config: Config) -> Result<(), Error> { Ok(()) }, - Err(err) => Err(Error::new(ErrorKind::InvalidData, format!("Failed to encode data into json format: {}", err.to_string()))) + Err(err) => Err(anyhow::anyhow!("Failed to encode data into json format: {}", err.to_string())) } }, - None => Err(Error::new(ErrorKind::NotFound, format!("Failed to get config file path"))) + None => Err(anyhow::anyhow!("Failed to get config file path")) } } /// Update config file from the in-memory saved config -pub fn flush() -> Result<(), Error> { +pub fn flush() -> anyhow::Result<()> { unsafe { match &CONFIG { Some(config) => update_raw(config.clone()), - None => Err(Error::new(ErrorKind::Other, "Config wasn't loaded into the memory")) + None => Err(anyhow::anyhow!("Config wasn't loaded into the memory")) } } } diff --git a/src/lib/dxvk.rs b/src/lib/dxvk.rs index 3c298c7..198f9ab 100644 --- a/src/lib/dxvk.rs +++ b/src/lib/dxvk.rs @@ -80,7 +80,7 @@ impl Version { std::path::Path::new(&format!("{}/{}", folder.to_string(), self.name)).exists() } - pub fn apply(&self, dxvks_folder: T, prefix_path: T) -> std::io::Result { + pub fn apply(&self, dxvks_folder: T, prefix_path: T) -> anyhow::Result { let apply_path = format!("{}/{}/setup_dxvk.sh", dxvks_folder.to_string(), self.name); let config = config::get()?; @@ -95,13 +95,18 @@ impl Version { None => (String::from("wine64"), String::from("wineserver"), String::from("wineboot")) }; - Dxvk::install( + let result = Dxvk::install( PathBuf::from(apply_path), PathBuf::from(prefix_path.to_string()), PathBuf::from(&wine_path), PathBuf::from(wine_path), PathBuf::from(wineboot_path), PathBuf::from(wineserver_path) - ) + ); + + match result { + Ok(output) => Ok(output), + Err(err) => Err(err.into()) + } } } diff --git a/src/ui/components/dxvk_row.rs b/src/ui/components/dxvk_row.rs index 658db33..0ac93f6 100644 --- a/src/ui/components/dxvk_row.rs +++ b/src/ui/components/dxvk_row.rs @@ -71,7 +71,7 @@ impl DxvkRow { } } - pub fn apply(&self, dxvks_folder: T, prefix_path: T) -> std::io::Result { + pub fn apply(&self, dxvks_folder: T, prefix_path: T) -> anyhow::Result { self.button.set_sensitive(false); self.apply_button.set_sensitive(false); diff --git a/src/ui/first_run/default_paths.rs b/src/ui/first_run/default_paths.rs index ec6487e..64d864b 100644 --- a/src/ui/first_run/default_paths.rs +++ b/src/ui/first_run/default_paths.rs @@ -44,7 +44,7 @@ pub struct Page { } impl Page { - pub fn new(window: gtk::Window) -> Result { + pub fn new(window: gtk::Window) -> anyhow::Result { let builder = gtk::Builder::from_resource("/org/app/ui/first_run/default_paths.ui"); let result = Self { @@ -62,10 +62,7 @@ impl Page { exit_button: get_object(&builder, "exit_button")? }; - let config = match config::get() { - Ok(config) => config, - Err(err) => return Err(err.to_string()) - }; + let config = config::get()?; // Add paths to subtitles result.runners_folder.set_subtitle(&config.game.wine.builds); diff --git a/src/ui/first_run/dependencies.rs b/src/ui/first_run/dependencies.rs index be91f2a..63c098b 100644 --- a/src/ui/first_run/dependencies.rs +++ b/src/ui/first_run/dependencies.rs @@ -15,7 +15,7 @@ pub struct Page { } impl Page { - pub fn new() -> Result { + pub fn new() -> anyhow::Result { let builder = gtk::Builder::from_resource("/org/app/ui/first_run/dependencies.ui"); let result = Self { diff --git a/src/ui/first_run/download_components.rs b/src/ui/first_run/download_components.rs index 27145c5..bc9ec90 100644 --- a/src/ui/first_run/download_components.rs +++ b/src/ui/first_run/download_components.rs @@ -25,7 +25,7 @@ pub struct Page { } impl Page { - pub fn new() -> Result { + pub fn new() -> anyhow::Result { let builder = gtk::Builder::from_resource("/org/app/ui/first_run/download_components.ui"); let mut result = Self { diff --git a/src/ui/first_run/finish.rs b/src/ui/first_run/finish.rs index c8013c6..d60faff 100644 --- a/src/ui/first_run/finish.rs +++ b/src/ui/first_run/finish.rs @@ -8,7 +8,7 @@ pub struct Page { } impl Page { - pub fn new() -> Result { + pub fn new() -> anyhow::Result { let builder = gtk::Builder::from_resource("/org/app/ui/first_run/finish.ui"); Ok(Self { diff --git a/src/ui/first_run/mod.rs b/src/ui/first_run/mod.rs index 650f2b0..1e581d9 100644 --- a/src/ui/first_run/mod.rs +++ b/src/ui/first_run/mod.rs @@ -48,7 +48,7 @@ pub struct AppWidgets { } impl AppWidgets { - pub fn try_get() -> Result { + pub fn try_get() -> anyhow::Result { let builder = gtk::Builder::from_resource("/org/app/ui/first_run.ui"); let result = Self { @@ -131,7 +131,7 @@ pub struct App { impl App { /// Create new application - pub fn new(app: >k::Application) -> Result { + pub fn new(app: >k::Application) -> anyhow::Result { // Get default widgets from ui file and add events to them let result = Self { widgets: AppWidgets::try_get()?, diff --git a/src/ui/first_run/tos_warning.rs b/src/ui/first_run/tos_warning.rs index 1e67763..c947723 100644 --- a/src/ui/first_run/tos_warning.rs +++ b/src/ui/first_run/tos_warning.rs @@ -8,7 +8,7 @@ pub struct Page { } impl Page { - pub fn new() -> Result { + pub fn new() -> anyhow::Result { let builder = gtk::Builder::from_resource("/org/app/ui/first_run/tos_warning.ui"); Ok(Self { diff --git a/src/ui/first_run/voice_packages.rs b/src/ui/first_run/voice_packages.rs index 865cd03..f6e3aad 100644 --- a/src/ui/first_run/voice_packages.rs +++ b/src/ui/first_run/voice_packages.rs @@ -18,7 +18,7 @@ pub struct Page { } impl Page { - pub fn new() -> Result { + pub fn new() -> anyhow::Result { let builder = gtk::Builder::from_resource("/org/app/ui/first_run/voice_packages.ui"); let mut result = Self { diff --git a/src/ui/first_run/welcome.rs b/src/ui/first_run/welcome.rs index 51f2850..bef4102 100644 --- a/src/ui/first_run/welcome.rs +++ b/src/ui/first_run/welcome.rs @@ -8,7 +8,7 @@ pub struct Page { } impl Page { - pub fn new() -> Result { + pub fn new() -> anyhow::Result { let builder = gtk::Builder::from_resource("/org/app/ui/first_run/welcome.ui"); Ok(Self { diff --git a/src/ui/main.rs b/src/ui/main.rs index 87d7985..441a331 100644 --- a/src/ui/main.rs +++ b/src/ui/main.rs @@ -30,6 +30,7 @@ use crate::lib::wine::{ Version as WineVersion, List as WineList }; +use crate::lib::prettify_bytes::prettify_bytes; /// This structure is used to describe widgets used in application /// @@ -49,6 +50,7 @@ pub struct AppWidgets { pub launcher_content: adw::PreferencesPage, pub icon: gtk::Image, + pub predownload_game: gtk::Button, pub launch_game: gtk::Button, pub open_preferences: gtk::Button, @@ -58,7 +60,7 @@ pub struct AppWidgets { } impl AppWidgets { - pub fn try_get() -> Result { + pub fn try_get() -> anyhow::Result { let builder = gtk::Builder::from_resource("/org/app/ui/main.ui"); let window = get_object::(&builder, "window")?; @@ -76,6 +78,7 @@ impl AppWidgets { launcher_content: get_object(&builder, "launcher_content")?, icon: get_object(&builder, "icon")?, + predownload_game: get_object(&builder, "predownload_game")?, launch_game: get_object(&builder, "launch_game")?, open_preferences: get_object(&builder, "open_preferences")?, @@ -150,6 +153,7 @@ pub enum Actions { OpenPreferencesPage, PreferencesGoBack, PerformButtonEvent, + PredownloadUpdate, RepairGame, ShowProgressBar, UpdateProgress { fraction: Rc, title: Rc }, @@ -195,7 +199,7 @@ pub struct App { impl App { /// Create new application - pub fn new(app: >k::Application) -> Result { + pub fn new(app: >k::Application) -> anyhow::Result { let mut result = Self { widgets: AppWidgets::try_get()?, values: Default::default(), @@ -256,6 +260,9 @@ impl App { // Go back button for preferences page self.widgets.preferences_stack.preferences_go_back.connect_clicked(Actions::PreferencesGoBack.into_fn(&self)); + // Predownload update + self.widgets.predownload_game.connect_clicked(Actions::PredownloadUpdate.into_fn(&self)); + // Launch game self.widgets.launch_game.connect_clicked(Actions::PerformButtonEvent.into_fn(&self)); @@ -593,6 +600,61 @@ impl App { } } + Actions::PredownloadUpdate => { + let values = this.values.take(); + let state = values.state.clone(); + + this.values.set(values); + + match config::get() { + Ok(config) => { + match state { + LauncherState::PredownloadAvailable { game, mut voices } => { + let (sender, receiver) = glib::MainContext::channel::(glib::PRIORITY_DEFAULT); + + let mut diffs: Vec = vec![game]; + + diffs.append(&mut voices); + + this.widgets.progress_bar.show(); + + std::thread::spawn(clone!(@strong this => move || { + for mut diff in diffs { + let sender = sender.clone(); + + #[allow(unused_must_use)] + let result = diff.download_in(config.launcher.temp.as_ref().unwrap(), move |curr, total| { + sender.send(InstallerUpdate::DownloadingProgress(curr, total)); + }); + + if let Err(err) = result { + let err: Error = err.into(); + + this.update(Actions::Toast(Rc::new(( + String::from("Downloading failed"), err.to_string() + )))).unwrap(); + + break; + } + } + + this.update(Actions::HideProgressBar).unwrap(); + })); + + receiver.attach(None, clone!(@strong this => move |state| { + this.widgets.progress_bar.update_from_state(state); + + glib::Continue(true) + })); + } + + _ => unreachable!() + } + }, + Err(err) => this.toast("Failed to load config", err) + } + } + Actions::RepairGame => { match config::get() { Ok(config) => { @@ -812,14 +874,49 @@ impl App { self.widgets.launch_game.remove_css_class("warning"); self.widgets.launch_game.remove_css_class("destructive-action"); + self.widgets.predownload_game.hide(); + match &state { LauncherState::Launch => { self.widgets.launch_game.set_label("Launch"); } - // TODO - LauncherState::PredownloadAvailable { .. } => { + LauncherState::PredownloadAvailable { game, voices } => { self.widgets.launch_game.set_label("Launch"); + + // Calculate size of the update + let size = + game.size().unwrap_or((0, 0)).0 + + voices.into_iter().fold(0, |acc, voice| acc + voice.size().unwrap_or((0, 0)).0); + + // Update tooltip + self.widgets.predownload_game.set_tooltip_text(Some(&format!("Pre-download {} update ({})", game.latest(), prettify_bytes(size)))); + + // Prepare button's color + self.widgets.predownload_game.remove_css_class("success"); + self.widgets.predownload_game.add_css_class("warning"); + self.widgets.predownload_game.set_sensitive(true); + + if let Ok(config) = config::get() { + if let Some(temp) = config.launcher.temp { + let tmp = PathBuf::from(temp); + + // If all the files were downloaded + let downloaded = + tmp.join(game.file_name().unwrap()).exists() && + voices.into_iter().fold(true, move |acc, voice| acc && tmp.join(voice.file_name().unwrap()).exists()); + + if downloaded { + self.widgets.predownload_game.remove_css_class("warning"); + self.widgets.predownload_game.add_css_class("success"); + self.widgets.predownload_game.set_sensitive(false); + + self.widgets.predownload_game.set_tooltip_text(Some(&format!("{} update is predownloaded ({})", game.latest(), prettify_bytes(size)))); + } + } + } + + self.widgets.predownload_game.show(); } LauncherState::PatchAvailable(patch) => { diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 0b0c967..6ce43a1 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -11,10 +11,10 @@ pub use first_run::App as FirstRunApp; pub use main::App as MainApp; /// This function loads object from builder or panics if it doesn't exist -pub fn get_object>(builder: >k::Builder, name: &str) -> Result { +pub fn get_object>(builder: >k::Builder, name: &str) -> anyhow::Result { match builder.object::(name) { Some(object) => Ok(object), - None => Err(format!("Failed to parse object '{}'", name)) + None => Err(anyhow::anyhow!("Failed to parse object '{name}'")) } } diff --git a/src/ui/preferences/enhancements.rs b/src/ui/preferences/enhancements.rs index dd8f34f..8f3b79d 100644 --- a/src/ui/preferences/enhancements.rs +++ b/src/ui/preferences/enhancements.rs @@ -48,7 +48,7 @@ pub struct AppWidgets { } impl AppWidgets { - fn try_get(window: &adw::ApplicationWindow) -> Result { + fn try_get(window: &adw::ApplicationWindow) -> anyhow::Result { let builder = gtk::Builder::from_resource("/org/app/ui/preferences/enhancements.ui"); let result = Self { @@ -123,7 +123,7 @@ pub struct App { impl App { /// Create new application - pub fn new(window: &adw::ApplicationWindow) -> Result { + pub fn new(window: &adw::ApplicationWindow) -> anyhow::Result { let result = Self { widgets: AppWidgets::try_get(window)? }.init_events(); @@ -301,7 +301,7 @@ impl App { } /// This method is being called by the `PreferencesStack::update` - pub fn prepare(&self, status_page: &adw::StatusPage) -> std::io::Result<()> { + pub fn prepare(&self, status_page: &adw::StatusPage) -> anyhow::Result<()> { let config = config::get()?; status_page.set_description(Some("Loading enhancements...")); diff --git a/src/ui/preferences/environment.rs b/src/ui/preferences/environment.rs index 1d97eb0..edd9a43 100644 --- a/src/ui/preferences/environment.rs +++ b/src/ui/preferences/environment.rs @@ -7,7 +7,6 @@ use gtk::glib::clone; use std::collections::HashMap; use std::rc::Rc; use std::cell::Cell; -use std::io::Error; use crate::ui::get_object; use crate::lib::config; @@ -31,7 +30,7 @@ pub struct AppWidgets { } impl AppWidgets { - fn try_get() -> Result { + fn try_get() -> anyhow::Result { let builder = gtk::Builder::from_resource("/org/app/ui/preferences/environment.ui"); let result = Self { @@ -89,7 +88,7 @@ pub struct App { impl App { /// Create new application - pub fn new() -> Result { + pub fn new() -> anyhow::Result { let result = Self { widgets: AppWidgets::try_get()?, values: Default::default(), @@ -225,7 +224,7 @@ impl App { } /// This method is being called by the `PreferencesStack::update` - pub fn prepare(&self, status_page: &adw::StatusPage) -> Result<(), Error> { + pub fn prepare(&self, status_page: &adw::StatusPage) -> anyhow::Result<()> { let config = config::get()?; status_page.set_description(Some("Loading environment...")); diff --git a/src/ui/preferences/gamescope.rs b/src/ui/preferences/gamescope.rs index 0569d50..1baec4f 100644 --- a/src/ui/preferences/gamescope.rs +++ b/src/ui/preferences/gamescope.rs @@ -33,7 +33,7 @@ pub struct AppWidgets { } impl AppWidgets { - fn try_get(window: &adw::ApplicationWindow) -> Result { + fn try_get(window: &adw::ApplicationWindow) -> anyhow::Result { let builder = gtk::Builder::from_resource("/org/app/ui/preferences/gamescope.ui"); let result = Self { @@ -79,7 +79,7 @@ pub struct App { impl App { /// Create new application - pub fn new(window: &adw::ApplicationWindow) -> Result { + pub fn new(window: &adw::ApplicationWindow) -> anyhow::Result { let result = Self { widgets: AppWidgets::try_get(window)? }.init_events(); @@ -221,7 +221,7 @@ impl App { } /// This method is being called by the `EnhancementsPage::prepare` - pub fn prepare(&self, status_page: &adw::StatusPage) -> std::io::Result<()> { + pub fn prepare(&self, status_page: &adw::StatusPage) -> anyhow::Result<()> { let config = config::get()?; status_page.set_description(Some("Loading gamescope...")); diff --git a/src/ui/preferences/general.rs b/src/ui/preferences/general.rs index 95473b5..7f4aa40 100644 --- a/src/ui/preferences/general.rs +++ b/src/ui/preferences/general.rs @@ -55,7 +55,7 @@ pub struct AppWidgets { } impl AppWidgets { - pub fn try_get() -> Result { + pub fn try_get() -> anyhow::Result { let builder = gtk::Builder::from_resource("/org/app/ui/preferences/general.ui"); let mut result = Self { @@ -84,16 +84,10 @@ impl AppWidgets { dxvk_components: Default::default() }; - let config = match config::get() { - Ok(config) => config, - Err(err) => return Err(err.to_string()) - }; + let config = config::get()?; // Update voiceovers list - let voice_packages = match VoicePackage::list_latest() { - Ok(voice_packages) => voice_packages, - Err(err) => return Err(err.to_string()) - }; + let voice_packages = VoicePackage::list_latest()?; let mut components = Vec::new(); @@ -197,7 +191,7 @@ pub struct App { impl App { /// Create new application - pub fn new() -> Result { + pub fn new() -> anyhow::Result { let result = Self { app: Default::default(), widgets: AppWidgets::try_get()?, diff --git a/src/ui/preferences/mod.rs b/src/ui/preferences/mod.rs index 87e8842..76a080d 100644 --- a/src/ui/preferences/mod.rs +++ b/src/ui/preferences/mod.rs @@ -38,7 +38,7 @@ pub struct PreferencesStack { } impl PreferencesStack { - pub fn new(window: &adw::ApplicationWindow) -> Result { + pub fn new(window: &adw::ApplicationWindow) -> anyhow::Result { let builder = gtk::Builder::from_resource("/org/app/ui/preferences.ui"); let result = Self { diff --git a/src/ui/traits/download_component.rs b/src/ui/traits/download_component.rs index d2fb3d4..b0375fc 100644 --- a/src/ui/traits/download_component.rs +++ b/src/ui/traits/download_component.rs @@ -25,7 +25,7 @@ pub trait DownloadComponent { Path::new(&self.get_component_path(installation_path)).exists() } - fn download(&self, installation_path: T) -> std::io::Result> { + fn download(&self, installation_path: T) -> anyhow::Result> { let (sender, receiver) = glib::MainContext::channel::(glib::PRIORITY_DEFAULT); let (progress_bar, button) = self.get_downloading_widgets();