Several changes

- updated core library and components library
- added (likely working) updates pre-downloading functionality
- moved to `anyhow::Result` in lots of places
This commit is contained in:
Observer KRypt0n_ 2022-09-26 15:08:29 +02:00
parent 7ac3935ab1
commit 002c77a9c1
No known key found for this signature in database
GPG key ID: 844DA47BA25FE1E2
23 changed files with 160 additions and 58 deletions

2
Cargo.lock generated
View file

@ -31,7 +31,7 @@ dependencies = [
[[package]]
name = "anime-game-core"
version = "1.1.0"
version = "1.1.4"
dependencies = [
"anyhow",
"bzip2",

@ -1 +1 @@
Subproject commit 6adbca88936bcb16d10063fe58688cc7b10813b2
Subproject commit e2140cea392560ac685088d6cc6295ad1172cb72

View file

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

@ -1 +1 @@
Subproject commit 59f7158df2b9a339fd62d4ee124f0ece47751e6b
Subproject commit 6ae731151cf1562877e5cb84896f3b5d3f001c6c

View file

@ -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<Config> = 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<Config, Error> {
pub fn get() -> anyhow::Result<Config> {
unsafe {
match &CONFIG {
Some(config) => Ok(config.clone()),
@ -51,7 +51,7 @@ pub fn get() -> Result<Config, Error> {
/// Get config data
///
/// This method will always load data directly from the file and update in-memory config
pub fn get_raw() -> Result<Config, Error> {
pub fn get_raw() -> anyhow::Result<Config> {
match config_file() {
Some(path) => {
// Try to read config if the file exists
@ -71,7 +71,7 @@ pub fn get_raw() -> Result<Config, Error> {
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<Config, Error> {
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"))
}
}
}

View file

@ -80,7 +80,7 @@ impl Version {
std::path::Path::new(&format!("{}/{}", folder.to_string(), self.name)).exists()
}
pub fn apply<T: ToString>(&self, dxvks_folder: T, prefix_path: T) -> std::io::Result<Output> {
pub fn apply<T: ToString>(&self, dxvks_folder: T, prefix_path: T) -> anyhow::Result<Output> {
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())
}
}
}

View file

@ -71,7 +71,7 @@ impl DxvkRow {
}
}
pub fn apply<T: ToString>(&self, dxvks_folder: T, prefix_path: T) -> std::io::Result<std::process::Output> {
pub fn apply<T: ToString>(&self, dxvks_folder: T, prefix_path: T) -> anyhow::Result<std::process::Output> {
self.button.set_sensitive(false);
self.apply_button.set_sensitive(false);

View file

@ -44,7 +44,7 @@ pub struct Page {
}
impl Page {
pub fn new(window: gtk::Window) -> Result<Self, String> {
pub fn new(window: gtk::Window) -> anyhow::Result<Self> {
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);

View file

@ -15,7 +15,7 @@ pub struct Page {
}
impl Page {
pub fn new() -> Result<Self, String> {
pub fn new() -> anyhow::Result<Self> {
let builder = gtk::Builder::from_resource("/org/app/ui/first_run/dependencies.ui");
let result = Self {

View file

@ -25,7 +25,7 @@ pub struct Page {
}
impl Page {
pub fn new() -> Result<Self, String> {
pub fn new() -> anyhow::Result<Self> {
let builder = gtk::Builder::from_resource("/org/app/ui/first_run/download_components.ui");
let mut result = Self {

View file

@ -8,7 +8,7 @@ pub struct Page {
}
impl Page {
pub fn new() -> Result<Self, String> {
pub fn new() -> anyhow::Result<Self> {
let builder = gtk::Builder::from_resource("/org/app/ui/first_run/finish.ui");
Ok(Self {

View file

@ -48,7 +48,7 @@ pub struct AppWidgets {
}
impl AppWidgets {
pub fn try_get() -> Result<Self, String> {
pub fn try_get() -> anyhow::Result<Self> {
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: &gtk::Application) -> Result<Self, String> {
pub fn new(app: &gtk::Application) -> anyhow::Result<Self> {
// Get default widgets from ui file and add events to them
let result = Self {
widgets: AppWidgets::try_get()?,

View file

@ -8,7 +8,7 @@ pub struct Page {
}
impl Page {
pub fn new() -> Result<Self, String> {
pub fn new() -> anyhow::Result<Self> {
let builder = gtk::Builder::from_resource("/org/app/ui/first_run/tos_warning.ui");
Ok(Self {

View file

@ -18,7 +18,7 @@ pub struct Page {
}
impl Page {
pub fn new() -> Result<Self, String> {
pub fn new() -> anyhow::Result<Self> {
let builder = gtk::Builder::from_resource("/org/app/ui/first_run/voice_packages.ui");
let mut result = Self {

View file

@ -8,7 +8,7 @@ pub struct Page {
}
impl Page {
pub fn new() -> Result<Self, String> {
pub fn new() -> anyhow::Result<Self> {
let builder = gtk::Builder::from_resource("/org/app/ui/first_run/welcome.ui");
Ok(Self {

View file

@ -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<Self, String> {
pub fn try_get() -> anyhow::Result<Self> {
let builder = gtk::Builder::from_resource("/org/app/ui/main.ui");
let window = get_object::<adw::ApplicationWindow>(&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<f64>, title: Rc<String> },
@ -195,7 +199,7 @@ pub struct App {
impl App {
/// Create new application
pub fn new(app: &gtk::Application) -> Result<Self, String> {
pub fn new(app: &gtk::Application) -> anyhow::Result<Self> {
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::<InstallerUpdate>(glib::PRIORITY_DEFAULT);
let mut diffs: Vec<VersionDiff> = 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) => {

View file

@ -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<T: IsA<gtk::glib::Object>>(builder: &gtk::Builder, name: &str) -> Result<T, String> {
pub fn get_object<T: IsA<gtk::glib::Object>>(builder: &gtk::Builder, name: &str) -> anyhow::Result<T> {
match builder.object::<T>(name) {
Some(object) => Ok(object),
None => Err(format!("Failed to parse object '{}'", name))
None => Err(anyhow::anyhow!("Failed to parse object '{name}'"))
}
}

View file

@ -48,7 +48,7 @@ pub struct AppWidgets {
}
impl AppWidgets {
fn try_get(window: &adw::ApplicationWindow) -> Result<Self, String> {
fn try_get(window: &adw::ApplicationWindow) -> anyhow::Result<Self> {
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<Self, String> {
pub fn new(window: &adw::ApplicationWindow) -> anyhow::Result<Self> {
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..."));

View file

@ -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<Self, String> {
fn try_get() -> anyhow::Result<Self> {
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<Self, String> {
pub fn new() -> anyhow::Result<Self> {
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..."));

View file

@ -33,7 +33,7 @@ pub struct AppWidgets {
}
impl AppWidgets {
fn try_get(window: &adw::ApplicationWindow) -> Result<Self, String> {
fn try_get(window: &adw::ApplicationWindow) -> anyhow::Result<Self> {
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<Self, String> {
pub fn new(window: &adw::ApplicationWindow) -> anyhow::Result<Self> {
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..."));

View file

@ -55,7 +55,7 @@ pub struct AppWidgets {
}
impl AppWidgets {
pub fn try_get() -> Result<Self, String> {
pub fn try_get() -> anyhow::Result<Self> {
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<Self, String> {
pub fn new() -> anyhow::Result<Self> {
let result = Self {
app: Default::default(),
widgets: AppWidgets::try_get()?,

View file

@ -38,7 +38,7 @@ pub struct PreferencesStack {
}
impl PreferencesStack {
pub fn new(window: &adw::ApplicationWindow) -> Result<Self, String> {
pub fn new(window: &adw::ApplicationWindow) -> anyhow::Result<Self> {
let builder = gtk::Builder::from_resource("/org/app/ui/preferences.ui");
let result = Self {

View file

@ -25,7 +25,7 @@ pub trait DownloadComponent {
Path::new(&self.get_component_path(installation_path)).exists()
}
fn download<T: ToString>(&self, installation_path: T) -> std::io::Result<Await<DownloadingResult>> {
fn download<T: ToString>(&self, installation_path: T) -> anyhow::Result<Await<DownloadingResult>> {
let (sender, receiver) = glib::MainContext::channel::<InstallerUpdate>(glib::PRIORITY_DEFAULT);
let (progress_bar, button) = self.get_downloading_widgets();