diff --git a/src/adb.rs b/src/adb.rs deleted file mode 100644 index 4b09cb1..0000000 --- a/src/adb.rs +++ /dev/null @@ -1,12 +0,0 @@ -use std::path::Path; - -use crate::cmd_runner::CmdRunner; - -pub fn get_adb_install_runner(path: &Path) -> CmdRunner { - path.try_exists().expect("APK file provided does not exist"); - CmdRunner::new( - None, - "adb".into(), - vec!["install".into(), path.to_string_lossy().to_string()], - ) -} diff --git a/src/downloader.rs b/src/downloader.rs index 4c01c8d..87cb093 100644 --- a/src/downloader.rs +++ b/src/downloader.rs @@ -3,8 +3,8 @@ use reqwest::{ header::{HeaderMap, USER_AGENT}, Method, }; -use std::{io::prelude::*, path::PathBuf, thread::JoinHandle}; -use std::{thread, time::Duration}; +use std::time::Duration; +use std::{io::prelude::*, path::Path}; const TIMEOUT: Duration = Duration::from_secs(60); const CHUNK_SIZE: usize = 1024; @@ -23,27 +23,21 @@ fn client() -> reqwest::blocking::Client { .expect("Failed to build reqwest::Client") } -pub fn download_file(url: String, path: PathBuf) -> JoinHandle> { - thread::spawn(move || { - let client = client(); - match client.request(Method::GET, url).send() { - Ok(res) => { - let status = res.status(); - if status.is_client_error() || status.is_server_error() { - return Err(res.error_for_status().unwrap_err()); - } - let mut writer = get_writer(&path).expect("Unable to write to path"); - for chunk in res - .bytes() - .expect("Could not get HTTP response bytes") - .chunks(CHUNK_SIZE) - { - writer.write_all(chunk).expect("Failed to write chunk"); - } - writer.flush().expect("Failed to flush download writer"); - Ok(()) - } - Err(e) => Err(e), - } - }) +pub fn download_file_sync(url: &str, path: &Path) -> Result<(), reqwest::Error> { + let client = client(); + let res = client.request(Method::GET, url).send()?; + let status = res.status(); + if status.is_client_error() || status.is_server_error() { + return Err(res.error_for_status().unwrap_err()); + } + let mut writer = get_writer(path).expect("Unable to write to path"); + for chunk in res + .bytes() + .expect("Could not get HTTP response bytes") + .chunks(CHUNK_SIZE) + { + writer.write_all(chunk).expect("Failed to write chunk"); + } + writer.flush().expect("Failed to flush download writer"); + Ok(()) } diff --git a/src/is_appimage.rs b/src/is_appimage.rs index d039ace..bbbf3e6 100644 --- a/src/is_appimage.rs +++ b/src/is_appimage.rs @@ -1,6 +1,5 @@ -use std::env; - use lazy_static::lazy_static; +use std::env; fn is_appimage() -> bool { env::var("APPIMAGE").is_ok_and(|s| !s.trim().is_empty()) diff --git a/src/main.rs b/src/main.rs index 558afa7..ecdfae9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,6 @@ use relm4::{ use steam_linux_runtime_injector::restore_runtime_entrypoint; use ui::app::{App, AppInit}; -pub mod adb; pub mod build_tools; pub mod builders; pub mod cmd_runner; diff --git a/src/ui/install_wivrn_box.rs b/src/ui/install_wivrn_box.rs index f0b8045..52daa4f 100644 --- a/src/ui/install_wivrn_box.rs +++ b/src/ui/install_wivrn_box.rs @@ -1,20 +1,27 @@ -use super::alert::alert; +use super::{ + alert::alert, + job_worker::{ + internal_worker::JobWorkerOut, + job::{FuncWorkerOut, WorkerJob}, + JobWorker, + }, +}; use crate::{ - adb::get_adb_install_runner, - cmd_runner::CmdRunner, dependencies::adb_dep::adb_dep, - downloader::download_file, + downloader::download_file_sync, paths::wivrn_apk_download_path, profile::{Profile, XRServiceType}, - runner::{Runner, RunnerStatus}, }; use gtk::prelude::*; +use gtk4::glib::clone; use relm4::{ actions::{ActionGroupName, RelmAction, RelmActionGroup}, new_action_group, new_stateless_action, prelude::*, }; -use std::thread::JoinHandle; +use std::collections::VecDeque; + +const DOWNLOAD_ERROR: &str = "download error"; #[derive(PartialEq, Eq, Debug, Clone)] pub enum InstallWivrnStatus { @@ -28,9 +35,9 @@ pub struct InstallWivrnBox { selected_profile: Profile, install_wivrn_status: InstallWivrnStatus, #[tracker::do_not_track] - download_thread: Option>>, + install_runner: Option, #[tracker::do_not_track] - install_runner: Option, + install_runner_log: String, #[tracker::do_not_track] root_win: gtk::Window, } @@ -38,10 +45,10 @@ pub struct InstallWivrnBox { #[allow(clippy::large_enum_variant)] #[derive(Debug)] pub enum InstallWivrnBoxMsg { - ClockTicking, UpdateSelectedProfile(Profile), - DownloadWivrn(String), - InstallWivrnApk, + InstallWivrnApk(String), + OnInstallRunnerLog(Vec), + OnInstallRunnerExit(i32), } #[derive(Debug)] @@ -128,70 +135,96 @@ impl SimpleComponent for InstallWivrnBox { self.reset(); match message { - Self::Input::ClockTicking => { - if self.download_thread.is_some() { - let finished = self.download_thread.as_ref().unwrap().is_finished(); - if finished { - let joinh = self.download_thread.take().unwrap(); - match joinh.join().unwrap() { - Ok(_) => { - sender.input(Self::Input::InstallWivrnApk); - } - Err(_) => self.set_install_wivrn_status(InstallWivrnStatus::Done( - Some("Error downloading WiVRn APK".into()), - )), - } - } - } - if self.install_runner.is_some() { - let runner = self.install_runner.as_mut().unwrap(); - let output = runner.consume_output().to_lowercase(); - let fallback_msg = - |code: i32| Some(format!("ADB exited with code \"{}\"", code)); - match runner.status() { - RunnerStatus::Running => {} - RunnerStatus::Stopped(status) => { - self.install_runner.take(); - self.set_install_wivrn_status(match status { - None | Some(0) => InstallWivrnStatus::Success, - Some(255) => { - InstallWivrnStatus::Done(Some(concat!( - "You need to authorize this computer to run developer ", - "commands in your headset. Authorize it and try again.", - ).into())) - } - Some(1) => { - if output.contains("no devices/emulators found") { - InstallWivrnStatus::Done(Some(concat!( - "No devices connected. Make sure your headset is connected ", - "to this computer and USB debugging is turned on." - ).into())) - } else { - InstallWivrnStatus::Done(fallback_msg(1)) - } - } - Some(err_code) => InstallWivrnStatus::Done(fallback_msg(err_code)), - }); - } - }; - } - } - Self::Input::DownloadWivrn(link) => { + Self::Input::InstallWivrnApk(link) => { if !adb_dep().check() { alert("ADB is not installed", Some("Please install ADB on your computer to install WiVRn on your Android headset"), Some(&self.root_win)); - } else { - self.set_install_wivrn_status(InstallWivrnStatus::InProgress); - self.download_thread = Some(download_file(link, wivrn_apk_download_path())); + return; } - } - Self::Input::InstallWivrnApk => { - let mut runner = get_adb_install_runner(&wivrn_apk_download_path()); + self.set_install_wivrn_status(InstallWivrnStatus::InProgress); + self.install_runner_log = String::default(); + let mut jobs = VecDeque::new(); + let wivrn_apk_path = wivrn_apk_download_path(); + jobs.push_back(WorkerJob::new_func(Box::new(clone!( + #[strong] + wivrn_apk_path, + move || { + if download_file_sync(&link, &wivrn_apk_path).is_err() { + FuncWorkerOut { + out: vec![DOWNLOAD_ERROR.into()], + success: false, + } + } else if wivrn_apk_path.is_file() { + FuncWorkerOut { + out: Vec::default(), + success: true, + } + } else { + FuncWorkerOut { + out: vec![format!( + "Provided apk path {} does not exist", + wivrn_apk_path.to_string_lossy() + )], + success: false, + } + } + } + )))); + jobs.push_back(WorkerJob::new_cmd( + None, + "adb".into(), + Some(vec![ + "install".into(), + wivrn_apk_path.to_string_lossy().to_string(), + ]), + )); + let runner = JobWorker::new( + jobs, + sender.input_sender(), + Box::new(move |e| match e { + JobWorkerOut::Log(log) => Self::Input::OnInstallRunnerLog(log), + JobWorkerOut::Exit(code) => Self::Input::OnInstallRunnerExit(code), + }), + ); runner.start(); self.install_runner = Some(runner); } Self::Input::UpdateSelectedProfile(p) => { self.set_selected_profile(p); } + Self::Input::OnInstallRunnerLog(rows) => { + self.install_runner_log.push('\n'); + self.install_runner_log.push_str(&rows.join("\n")); + } + Self::Input::OnInstallRunnerExit(code) => { + self.set_install_wivrn_status(match code { + 0 => InstallWivrnStatus::Success, + 255 => InstallWivrnStatus::Done(Some( + concat!( + "You need to authorize this computer to run developer ", + "commands in your headset. Authorize it and try again.", + ) + .into(), + )), + 1 if self.install_runner_log.contains(DOWNLOAD_ERROR) => { + InstallWivrnStatus::Done(Some("Error downloading WiVRn APK".into())) + } + 1 if self + .install_runner_log + .contains("no devices/emulators found") => + { + InstallWivrnStatus::Done(Some( + concat!( + "No devices connected. Make sure your headset is connected ", + "to this computer and USB debugging is turned on." + ) + .into(), + )) + } + _ => { + InstallWivrnStatus::Done(Some(format!("ADB exited with code \"{}\"", code))) + } + }); + } } } @@ -203,8 +236,8 @@ impl SimpleComponent for InstallWivrnBox { let model = Self { selected_profile: init.selected_profile, install_wivrn_status: InstallWivrnStatus::Done(None), - download_thread: None, install_runner: None, + install_runner_log: String::default(), root_win: init.root_win, tracker: 0, }; @@ -216,7 +249,7 @@ impl SimpleComponent for InstallWivrnBox { let apk_oculus_action = { let oculus_sender = sender.clone(); RelmAction::::new_stateless(move |_| { - oculus_sender.input(Self::Input::DownloadWivrn( + oculus_sender.input(Self::Input::InstallWivrnApk( "https://github.com/Meumeu/WiVRn/releases/latest/download/WiVRn-standard-release.apk".into() )); }) @@ -225,7 +258,7 @@ impl SimpleComponent for InstallWivrnBox { let apk_pico_action = { let pico_sender = sender.clone(); RelmAction::::new_stateless(move |_| { - pico_sender.input(Self::Input::DownloadWivrn( + pico_sender.input(Self::Input::InstallWivrnApk( "https://github.com/Meumeu/WiVRn/releases/latest/download/WiVRn-pico-release.apk".into() )); }) diff --git a/src/ui/main_view.rs b/src/ui/main_view.rs index b51e29e..0f5766c 100644 --- a/src/ui/main_view.rs +++ b/src/ui/main_view.rs @@ -436,11 +436,7 @@ impl SimpleComponent for MainView { self.reset(); match message { - Self::Input::ClockTicking => { - self.install_wivrn_box - .sender() - .emit(InstallWivrnBoxMsg::ClockTicking); - } + Self::Input::ClockTicking => {} Self::Input::StartStopClicked => { sender .output(Self::Output::DoStartStopXRService)