can now download the game

This commit is contained in:
ALEZ-DEV 2025-03-14 22:43:31 +01:00
parent 1a6bb86cb5
commit 80836f32ec
4 changed files with 391 additions and 37 deletions

View File

@ -14,10 +14,13 @@ use wincompatlib::prelude::Proton;
use crate::ui::{ use crate::ui::{
self, self,
pages::steps::{ pages::{
self,
steps::{
self, self,
download_components::{self, DownloadComponentsPageWidgets}, download_components::{self, DownloadComponentsPageWidgets},
}, },
},
}; };
static PROTON: OnceCell<Proton> = OnceCell::const_new(); static PROTON: OnceCell<Proton> = OnceCell::const_new();
@ -51,6 +54,7 @@ pub enum HandleGameProcessMsg {
RunGame, RunGame,
} }
#[derive(Debug)]
pub struct HandleGameProcess; pub struct HandleGameProcess;
impl Worker for HandleGameProcess { impl Worker for HandleGameProcess {
@ -81,6 +85,114 @@ impl Worker for HandleGameProcess {
} }
} }
#[derive(Debug)]
pub enum HandleGameInstallationMsg {
StartInstallation(Arc<pages::game::ProgressBarGameInstallationReporter>),
StartPatch,
StartUpdate(Arc<pages::game::ProgressBarGameInstallationReporter>),
}
#[derive(Debug)]
pub struct HandleGameInstallation;
impl Worker for HandleGameInstallation {
type Init = ();
type Input = HandleGameInstallationMsg;
type Output = pages::game::GamePageMsg;
fn init(init: Self::Init, sender: relm4::ComponentSender<Self>) -> Self {
Self
}
fn update(&mut self, message: Self::Input, sender: relm4::ComponentSender<Self>) {
match message {
HandleGameInstallationMsg::StartInstallation(progress_bar) => {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
sender.output(pages::game::GamePageMsg::SetIsDownloading(true));
let game_dir = if let Some(dir) = GameConfig::get_config().await.game_dir {
dir
} else {
GameConfig::get_config_directory().await
};
if let Err(error) = GameManager::install_game(game_dir, progress_bar).await
{
sender.output(pages::game::GamePageMsg::ShowError(format!(
"Error while downloading the game : {}",
error
)));
};
sender.output(pages::game::GamePageMsg::SetIsDownloading(false));
});
}
HandleGameInstallationMsg::StartPatch => {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
sender.output(pages::game::GamePageMsg::SetIsPatching(true));
let game_dir = if let Some(dir) = GameConfig::get_config().await.game_dir {
dir
} else {
GameConfig::get_config_directory().await
};
if let Err(error) = GameManager::patch_game(game_dir).await {
sender.output(pages::game::GamePageMsg::ShowError(format!(
"Error while patching the game : {}",
error
)));
};
sender.output(pages::game::GamePageMsg::SetIsPatching(false));
});
}
HandleGameInstallationMsg::StartUpdate(progress_bar) => {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
sender.output(pages::game::GamePageMsg::SetIsDownloading(true));
if let Err(error) = GameManager::update_game().await {
sender.output(pages::game::GamePageMsg::ShowError(format!(
"Error while updating the game : {}",
error
)));
};
let game_dir = if let Some(dir) = GameConfig::get_config().await.game_dir {
dir
} else {
GameConfig::get_config_directory().await
};
if let Err(error) = GameManager::install_game(game_dir, progress_bar).await
{
sender.output(pages::game::GamePageMsg::ShowError(format!(
"Error while updating the game : {}",
error
)));
};
sender.output(pages::game::GamePageMsg::SetIsDownloading(true));
});
}
}
}
}
#[derive(Debug)] #[derive(Debug)]
pub enum HandleComponentInstallationMsg { pub enum HandleComponentInstallationMsg {
StartInstallation( StartInstallation(

View File

@ -4,6 +4,7 @@ use crate::{manager, IS_DEVEL};
use babylonia_terminal_sdk::game_state::GameState; use babylonia_terminal_sdk::game_state::GameState;
use log::debug; use log::debug;
use pages::game::GamePageMsg;
use relm4::{ use relm4::{
self, self,
component::AsyncConnector, component::AsyncConnector,
@ -51,7 +52,7 @@ impl MainWindow {
.forward(sender.input_sender(), identity); .forward(sender.input_sender(), identity);
let game_page = pages::game::GamePage::builder() let game_page = pages::game::GamePage::builder()
.launch(()) .launch(game_state.clone())
.forward(sender.input_sender(), identity); .forward(sender.input_sender(), identity);
let about_page = pages::about::AboutPage::builder().launch(()); let about_page = pages::about::AboutPage::builder().launch(());
@ -269,6 +270,7 @@ impl SimpleAsyncComponent for MainWindow {
} }
MainWindowMsg::UpdateGameState => { MainWindowMsg::UpdateGameState => {
self.game_state = GameState::get_current_state().await.unwrap(); self.game_state = GameState::get_current_state().await.unwrap();
self.game_page.sender().send(GamePageMsg::UpdateGameState);
debug!( debug!(
"is_environment_ready : {}", "is_environment_ready : {}",
self.game_state.is_environment_ready() self.game_state.is_environment_ready()

View File

@ -1,29 +1,46 @@
use std::convert::identity; use std::{convert::identity, fmt::format};
use libadwaita::prelude::PreferencesPageExt; use arboard::Clipboard;
use babylonia_terminal_sdk::game_state::GameState;
use libadwaita::prelude::{MessageDialogExt, PreferencesPageExt};
use log::error;
use relm4::{ use relm4::{
adw, adw,
gtk::{ gtk::{
self, self,
prelude::{ButtonExt, WidgetExt}, prelude::{ButtonExt, GtkWindowExt, OrientableExt, WidgetExt},
}, },
prelude::{AsyncComponentParts, SimpleAsyncComponent}, prelude::{AsyncComponentParts, SimpleAsyncComponent},
Component, RelmWidgetExt, WorkerController, Component, RelmWidgetExt, WorkerController,
}; };
use crate::{manager, ui::MainWindowMsg, APP_RESOURCE_PATH}; use crate::{
manager,
//use adw::prelude::*; ui::{MainWindowMsg, MAIN_WINDOW},
//use libadwaita as adw; APP_RESOURCE_PATH,
};
#[derive(Debug)]
pub struct GamePage { pub struct GamePage {
game_state: GameState,
game_handler: WorkerController<manager::HandleGameProcess>, game_handler: WorkerController<manager::HandleGameProcess>,
installation_handler: WorkerController<manager::HandleGameInstallation>,
is_game_running: bool, is_game_running: bool,
is_downloading: bool,
is_patching: bool,
progress_bar_reporter: std::sync::Arc<ProgressBarGameInstallationReporter>,
progress_bar_message: String,
fraction: f64,
} }
#[derive(Debug)] #[derive(Debug)]
pub enum GamePageMsg { pub enum GamePageMsg {
SetIsGameRunning(bool), SetIsGameRunning(bool),
SetIsDownloading(bool),
SetIsPatching(bool),
UpdateGameState,
UpdateProgressBar(u64, u64),
ShowError(String),
} }
#[relm4::component(pub, async)] #[relm4::component(pub, async)]
@ -32,9 +49,12 @@ impl SimpleAsyncComponent for GamePage {
type Output = MainWindowMsg; type Output = MainWindowMsg;
type Init = (); type Init = GameState;
view! { view! {
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
adw::PreferencesPage { adw::PreferencesPage {
add = &adw::PreferencesGroup { add = &adw::PreferencesGroup {
gtk::Picture { gtk::Picture {
@ -53,6 +73,38 @@ impl SimpleAsyncComponent for GamePage {
add = &adw::PreferencesGroup { add = &adw::PreferencesGroup {
set_margin_vertical: 48, set_margin_vertical: 48,
gtk::Button {
set_css_classes: &["suggested-action", "pill"],
set_label: "Install",
set_hexpand: false,
set_width_request: 200,
set_visible: model.game_state == GameState::GameNotInstalled,
#[watch]
set_sensitive: !model.is_downloading,
connect_clicked[sender = model.installation_handler.sender().clone(), progress_bar = model.progress_bar_reporter.clone()] => move |_| {
sender.send(manager::HandleGameInstallationMsg::StartInstallation(progress_bar.clone())).unwrap();
},
},
gtk::Button {
set_css_classes: &["suggested-action", "pill"],
set_label: "Apply patch",
set_hexpand: false,
set_width_request: 200,
set_visible: model.game_state == GameState::GameNotPatched,
#[watch]
set_sensitive: !model.is_patching,
connect_clicked[sender = model.installation_handler.sender().clone()] => move |_| {
sender.send(manager::HandleGameInstallationMsg::StartPatch).unwrap();
},
},
gtk::Button { gtk::Button {
set_css_classes: &["suggested-action", "pill"], set_css_classes: &["suggested-action", "pill"],
@ -60,26 +112,123 @@ impl SimpleAsyncComponent for GamePage {
set_hexpand: false, set_hexpand: false,
set_width_request: 200, set_width_request: 200,
set_visible: model.game_state == GameState::GameInstalled,
#[watch] #[watch]
set_sensitive: !model.is_game_running, set_sensitive: !model.is_game_running,
connect_clicked[sender = model.game_handler.sender().clone()] => move |_| { connect_clicked[sender = model.game_handler.sender().clone()] => move |_| {
sender.send(manager::HandleGameProcessMsg::RunGame).unwrap(); sender.send(manager::HandleGameProcessMsg::RunGame).unwrap();
}, },
}, },
gtk::Button {
set_css_classes: &["suggested-action", "pill"],
set_label: "Update",
set_hexpand: false,
set_width_request: 200,
set_visible: model.game_state == GameState::GameNeedUpdate,
#[watch]
set_sensitive: !model.is_downloading,
connect_clicked[sender = model.installation_handler.sender().clone(), progress_bar = model.progress_bar_reporter.clone()] => move |_| {
sender.send(manager::HandleGameInstallationMsg::StartUpdate(progress_bar.clone())).unwrap();
}, },
}, },
},
},
gtk::ProgressBar {
#[watch]
set_fraction: model.fraction,
#[watch]
set_visible: (model.game_state == GameState::GameNotInstalled || model.game_state == GameState::GameNeedUpdate) && model.is_downloading && model.fraction != 0.0,
#[watch]
set_text: Some(&model.progress_bar_message),
set_show_text: true,
},
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
#[watch]
set_visible: (model.game_state == GameState::GameNotInstalled || model.game_state == GameState::GameNeedUpdate) && model.is_downloading && model.fraction == 0.0,
gtk::Box {
set_orientation: gtk::Orientation::Horizontal,
set_halign: gtk::Align::Center,
set_margin_bottom: 24,
gtk::Label {
set_label: "Checking game files",
add_css_class: "title-2",
},
gtk::Spinner {
set_spinning: true,
set_margin_start: 24,
}
},
gtk::Label {
set_label: "This can take some time, please wait...",
add_css_class: "title-4",
},
},
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
#[watch]
set_visible: model.game_state == GameState::GameNotPatched && model.is_patching,
gtk::Box {
set_orientation: gtk::Orientation::Horizontal,
gtk::Label {
set_label: "Patching game",
add_css_class: "title-2",
},
gtk::Spinner {
set_spinning: true,
}
},
gtk::Label {
set_label: "This can take some time, please wait...",
add_css_class: "title-4",
},
}
}
} }
async fn init( async fn init(
_: Self::Init, game_state: Self::Init,
root: Self::Root, root: Self::Root,
sender: relm4::AsyncComponentSender<Self>, sender: relm4::AsyncComponentSender<Self>,
) -> relm4::prelude::AsyncComponentParts<Self> { ) -> relm4::prelude::AsyncComponentParts<Self> {
let model = GamePage { let model = GamePage {
progress_bar_reporter: ProgressBarGameInstallationReporter::create(sender.clone()),
game_state,
game_handler: manager::HandleGameProcess::builder() game_handler: manager::HandleGameProcess::builder()
.detach_worker(()) .detach_worker(())
.forward(sender.input_sender(), identity), .forward(sender.input_sender(), identity),
installation_handler: manager::HandleGameInstallation::builder()
.detach_worker(())
.forward(sender.input_sender(), identity),
is_game_running: false, is_game_running: false,
is_downloading: false,
is_patching: false,
fraction: 0.0,
progress_bar_message: String::new(),
}; };
let widgets = view_output!(); let widgets = view_output!();
@ -90,6 +239,96 @@ impl SimpleAsyncComponent for GamePage {
async fn update(&mut self, message: Self::Input, _: relm4::AsyncComponentSender<Self>) -> () { async fn update(&mut self, message: Self::Input, _: relm4::AsyncComponentSender<Self>) -> () {
match message { match message {
GamePageMsg::SetIsGameRunning(value) => self.is_game_running = value, GamePageMsg::SetIsGameRunning(value) => self.is_game_running = value,
GamePageMsg::SetIsDownloading(value) => self.is_downloading = value,
GamePageMsg::SetIsPatching(value) => self.is_patching = value,
GamePageMsg::UpdateGameState => {
self.game_state = GameState::get_current_state().await.unwrap()
} //TODO: remove unwrap()
GamePageMsg::UpdateProgressBar(current, max_progress) => {
self.fraction = if current == 0 {
0.0
} else {
current as f64 / max_progress as f64
};
//1024^3 = 1073741824
let current_gb = current as f64 / 1073741824 as f64;
let max_gb = max_progress as f64 / 1073741824 as f64;
self.progress_bar_message = format!(
"Downloading : {:.2}% ({:.2} / {:.2}GiB)",
self.fraction * 100 as f64,
current_gb,
max_gb,
);
}
GamePageMsg::ShowError(message) => {
let dialog = unsafe {
adw::MessageDialog::new(
MAIN_WINDOW.as_ref(),
Some("Something went wrong"),
Some(&message),
)
};
dialog.add_response("close", "Close");
dialog.add_response("copy", "Copy");
dialog.set_response_appearance("copy", adw::ResponseAppearance::Suggested);
dialog.connect_response(Some("copy"), move |_, _| {
if let Err(err) = Clipboard::new().unwrap().set_text(&message.clone()) {
error!("Failed to copy the error to the clipboard : {}", err);
}
});
dialog.present();
} }
} }
} }
}
#[derive(Debug)]
struct ProgressBarGameInstallationReporterPrivate {
max_progress: Option<u64>,
}
#[derive(Debug)]
pub struct ProgressBarGameInstallationReporter {
private: std::sync::Mutex<Option<ProgressBarGameInstallationReporterPrivate>>,
sender: relm4::AsyncComponentSender<GamePage>,
}
impl ProgressBarGameInstallationReporter {
fn create(page: relm4::AsyncComponentSender<GamePage>) -> std::sync::Arc<Self> {
std::sync::Arc::new(Self {
private: std::sync::Mutex::new(None),
sender: page,
})
}
}
impl downloader::progress::Reporter for ProgressBarGameInstallationReporter {
fn setup(&self, max_progress: Option<u64>, message: &str) {
let private = ProgressBarGameInstallationReporterPrivate { max_progress };
let mut guard = self.private.lock().unwrap();
*guard = Some(private);
}
fn progress(&self, current: u64) {
if let Some(p) = self.private.lock().unwrap().as_mut() {
self.sender.input(GamePageMsg::UpdateProgressBar(
current,
p.max_progress.unwrap(),
));
}
}
fn set_message(&self, _: &str) {}
fn done(&self) {
let mut guard = self.private.lock().unwrap();
*guard = None;
}
}

View File

@ -251,7 +251,7 @@ impl SimpleAsyncComponent for DownloadComponentsPage {
}, },
gtk::Box { gtk::Box {
set_valign: gtk::Align::Center, set_halign: gtk::Align::Center,
set_orientation: gtk::Orientation::Horizontal, set_orientation: gtk::Orientation::Horizontal,
gtk::Label { gtk::Label {
@ -265,6 +265,7 @@ impl SimpleAsyncComponent for DownloadComponentsPage {
gtk::Spinner { gtk::Spinner {
set_spinning: true, set_spinning: true,
set_margin_start: 24,
#[watch] #[watch]
set_visible: model.currently_installing == CurrentlyInstalling::Fonts || model.currently_installing == CurrentlyInstalling::Denpendecies, set_visible: model.currently_installing == CurrentlyInstalling::Fonts || model.currently_installing == CurrentlyInstalling::Denpendecies,