feat!: installing wivrn apk will use a version matching the server
Some checks are pending
/ cargo-fmtcheck (push) Waiting to run
/ cargo-clippy (push) Waiting to run
/ cargo-test (push) Waiting to run
/ appimage (push) Waiting to run

This commit is contained in:
Gabriele Musco 2024-08-24 11:36:17 +02:00
commit 7daa5987b5

View file

@ -1,22 +1,61 @@
use super::{ use super::alert::alert;
alert::alert,
job_worker::{
internal_worker::JobWorkerOut,
job::{FuncWorkerOut, WorkerJob},
JobWorker,
},
};
use crate::{ use crate::{
async_process::async_process,
dependencies::adb_dep::adb_dep, dependencies::adb_dep::adb_dep,
downloader::download_file_sync, downloader::{cache_file, cache_file_path},
paths::wivrn_apk_download_path,
profile::{Profile, XRServiceType}, profile::{Profile, XRServiceType},
}; };
use gtk::{glib::clone, prelude::*}; use gtk::prelude::*;
use relm4::{new_action_group, new_stateless_action, prelude::*}; use relm4::{new_action_group, new_stateless_action, prelude::*};
use std::collections::VecDeque; use std::fs::remove_file;
const DOWNLOAD_ERROR: &str = "download error"; const ADB_ERR_NO_DEV: &str = "no devices/emulators found";
const WIVRN_LATEST_RELEASE_APK_URL: &str =
"https://github.com/WiVRn/WiVRn/releases/latest/download/WiVRn-standard-release.apk";
#[derive(Debug)]
enum GetWivrnApkRefErr {
NotWivrn,
RepoDirNotFound,
RepoManipulationFailed(git2::Error),
}
#[derive(Debug, Clone)]
enum WivrnApkRef {
Tag(String),
Commit(String),
}
fn get_wivrn_apk_ref(p: &Profile) -> Result<WivrnApkRef, GetWivrnApkRefErr> {
if p.xrservice_type != XRServiceType::Wivrn {
Err(GetWivrnApkRefErr::NotWivrn)
} else if !p.xrservice_path.is_dir() {
Err(GetWivrnApkRefErr::RepoDirNotFound)
} else {
let repo = git2::Repository::open(&p.xrservice_path)
.map_err(GetWivrnApkRefErr::RepoManipulationFailed)?;
if let Ok(tag) = repo
.describe(
git2::DescribeOptions::new()
.describe_tags()
.max_candidates_tags(0),
)
.and_then(|d| d.format(None))
{
Ok(WivrnApkRef::Tag(tag))
} else {
Ok(WivrnApkRef::Commit(
repo.head()
.map_err(GetWivrnApkRefErr::RepoManipulationFailed)?
.peel_to_commit()
.map_err(GetWivrnApkRefErr::RepoManipulationFailed)?
.id()
.to_string(),
))
}
}
}
#[derive(PartialEq, Eq, Debug, Clone)] #[derive(PartialEq, Eq, Debug, Clone)]
pub enum InstallWivrnStatus { pub enum InstallWivrnStatus {
@ -30,10 +69,6 @@ pub struct InstallWivrnBox {
selected_profile: Profile, selected_profile: Profile,
install_wivrn_status: InstallWivrnStatus, install_wivrn_status: InstallWivrnStatus,
#[tracker::do_not_track] #[tracker::do_not_track]
install_runner: Option<JobWorker>,
#[tracker::do_not_track]
install_runner_log: String,
#[tracker::do_not_track]
root_win: gtk::Window, root_win: gtk::Window,
} }
@ -41,9 +76,10 @@ pub struct InstallWivrnBox {
#[derive(Debug)] #[derive(Debug)]
pub enum InstallWivrnBoxMsg { pub enum InstallWivrnBoxMsg {
UpdateSelectedProfile(Profile), UpdateSelectedProfile(Profile),
InstallWivrnApk(String), /// prepares state for async action, calls DoInstall
OnInstallRunnerLog(Vec<String>), InstallWivrnApk,
OnInstallRunnerExit(i32), /// dowloads, installs, sets state back to what it needs to be
DoInstall(String),
} }
#[derive(Debug)] #[derive(Debug)]
@ -94,9 +130,7 @@ impl AsyncComponent for InstallWivrnBox {
#[track = "model.changed(Self::install_wivrn_status())"] #[track = "model.changed(Self::install_wivrn_status())"]
set_sensitive: model.install_wivrn_status != InstallWivrnStatus::InProgress, set_sensitive: model.install_wivrn_status != InstallWivrnStatus::InProgress,
connect_clicked[sender] => move |_| { connect_clicked[sender] => move |_| {
sender.input(Self::Input::InstallWivrnApk( sender.input(Self::Input::InstallWivrnApk)
"https://github.com/WiVRn/WiVRn/releases/latest/download/WiVRn-standard-release.apk".into()
))
}, },
}, },
gtk::Label { gtk::Label {
@ -131,82 +165,70 @@ impl AsyncComponent for InstallWivrnBox {
self.reset(); self.reset();
match message { match message {
Self::Input::InstallWivrnApk(link) => { Self::Input::InstallWivrnApk => {
if !adb_dep().check() { 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)); alert("ADB is not installed", Some("Please install ADB on your computer to install WiVRn on your Android headset"), Some(&self.root_win));
return; return;
} }
self.set_install_wivrn_status(InstallWivrnStatus::InProgress); self.set_install_wivrn_status(InstallWivrnStatus::InProgress);
self.install_runner_log = String::default();
let mut jobs = VecDeque::new(); match get_wivrn_apk_ref(&self.selected_profile) {
let wivrn_apk_path = wivrn_apk_download_path(); Err(GetWivrnApkRefErr::NotWivrn) => {
jobs.push_back(WorkerJob::new_func(Box::new(clone!( eprintln!("This is not a WiVRn profile, how did you get here?");
#[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() { Err(GetWivrnApkRefErr::RepoDirNotFound) => {
FuncWorkerOut { self.set_install_wivrn_status(InstallWivrnStatus::Done(Some(
out: Vec::default(), "Could not open WiVRn repository. Please build the profile before attempting to install the client APK".into()
success: true, )));
} }
} else { Err(GetWivrnApkRefErr::RepoManipulationFailed(giterr)) => {
FuncWorkerOut { eprintln!("Error: failed to manipulate WiVRn repo: {giterr}, falling back to latest release APK");
out: vec![format!( let existing = cache_file_path(WIVRN_LATEST_RELEASE_APK_URL, Some("apk"));
"Provided apk path {} does not exist", if existing.is_file() {
wivrn_apk_path.to_string_lossy() if let Err(e) = remove_file(&existing) {
)], eprintln!(
success: false, "Failed to remove file {}: {e}",
} existing.to_string_lossy()
}
}
))));
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) => { sender.input(Self::Input::DoInstall(WIVRN_LATEST_RELEASE_APK_URL.into()));
self.install_runner_log.push('\n');
self.install_runner_log.push_str(&rows.join("\n"));
} }
Self::Input::OnInstallRunnerExit(code) => { Ok(WivrnApkRef::Tag(tag)) => {
self.set_install_wivrn_status(match code { sender.input(Self::Input::DoInstall(
0 => InstallWivrnStatus::Success, format!("https://github.com/WiVRn/WiVRn/releases/download/{tag}/WiVRn-standard-release.apk")
255 => InstallWivrnStatus::Done(Some( ));
concat!( }
"You need to authorize this computer to run developer ", Ok(WivrnApkRef::Commit(commit)) => {
"commands in your headset. Authorize it and try again.", sender.input(Self::Input::DoInstall(
format!("https://github.com/WiVRn/WiVRn-APK/releases/download/apk-${commit}/org.meumeu.wivrn-standard-release.apk")
));
}
};
}
Self::Input::DoInstall(url) => {
// TODO: we gonna cache or just download async every time?
match cache_file(&url, Some("apk")).await {
Err(e) => {
eprintln!("Failed to download apk: {e}");
self.set_install_wivrn_status(InstallWivrnStatus::Done(Some(
"Error downloading WiVRn client APK".into(),
)));
}
Ok(apk_path) => {
self.set_install_wivrn_status(match async_process(
"adb",
Some(&["install", apk_path.to_string_lossy().to_string().as_str()]),
None,
) )
.into(), .await
)), {
1 if self.install_runner_log.contains(DOWNLOAD_ERROR) => { Ok(out) if out.exit_code == 0 => {
InstallWivrnStatus::Done(Some("Error downloading WiVRn APK".into())) InstallWivrnStatus::Success
} }
1 if self Ok(out) => {
.install_runner_log if out.stdout.contains(ADB_ERR_NO_DEV)
.contains("no devices/emulators found") => || out.stderr.contains(ADB_ERR_NO_DEV)
{ {
InstallWivrnStatus::Done(Some( InstallWivrnStatus::Done(Some(
concat!( concat!(
@ -215,12 +237,26 @@ impl AsyncComponent for InstallWivrnBox {
) )
.into(), .into(),
)) ))
} else {
eprintln!("Error: ADB failed with code {}.\nstdout:\n{}\n======\nstderr:\n{}", out.exit_code, out.stdout, out.stderr);
InstallWivrnStatus::Done(Some(
format!("ADB exited with code \"{}\"", out.exit_code)
))
} }
_ => { }
InstallWivrnStatus::Done(Some(format!("ADB exited with code \"{}\"", code))) Err(e) => {
eprintln!("Error: failed to run ADB: {e}");
InstallWivrnStatus::Done(Some(
"Failed to run ADB".into()
))
} }
}); });
} }
};
}
Self::Input::UpdateSelectedProfile(p) => {
self.set_selected_profile(p);
}
} }
} }
@ -232,8 +268,6 @@ impl AsyncComponent for InstallWivrnBox {
let model = Self { let model = Self {
selected_profile: init.selected_profile, selected_profile: init.selected_profile,
install_wivrn_status: InstallWivrnStatus::Done(None), install_wivrn_status: InstallWivrnStatus::Done(None),
install_runner: None,
install_runner_log: String::default(),
root_win: init.root_win, root_win: init.root_win,
tracker: 0, tracker: 0,
}; };
@ -246,4 +280,3 @@ impl AsyncComponent for InstallWivrnBox {
new_action_group!(pub InstallWivrnActionGroup, "installwivrn"); new_action_group!(pub InstallWivrnActionGroup, "installwivrn");
new_stateless_action!(pub WivrnApkStandardAction, InstallWivrnActionGroup, "apkoculus"); new_stateless_action!(pub WivrnApkStandardAction, InstallWivrnActionGroup, "apkoculus");
new_stateless_action!(pub WivrnApkPicoAction, InstallWivrnActionGroup, "apkpico");