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,96 +165,98 @@ 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, Err(GetWivrnApkRefErr::RepoDirNotFound) => {
move || { self.set_install_wivrn_status(InstallWivrnStatus::Done(Some(
if download_file_sync(&link, &wivrn_apk_path).is_err() { "Could not open WiVRn repository. Please build the profile before attempting to install the client APK".into()
FuncWorkerOut { )));
out: vec![DOWNLOAD_ERROR.into()], }
success: false, Err(GetWivrnApkRefErr::RepoManipulationFailed(giterr)) => {
} eprintln!("Error: failed to manipulate WiVRn repo: {giterr}, falling back to latest release APK");
} else if wivrn_apk_path.is_file() { let existing = cache_file_path(WIVRN_LATEST_RELEASE_APK_URL, Some("apk"));
FuncWorkerOut { if existing.is_file() {
out: Vec::default(), if let Err(e) = remove_file(&existing) {
success: true, eprintln!(
} "Failed to remove file {}: {e}",
} else { existing.to_string_lossy()
FuncWorkerOut { );
out: vec![format!(
"Provided apk path {} does not exist",
wivrn_apk_path.to_string_lossy()
)],
success: false,
} }
} }
sender.input(Self::Input::DoInstall(WIVRN_LATEST_RELEASE_APK_URL.into()));
} }
)))); Ok(WivrnApkRef::Tag(tag)) => {
jobs.push_back(WorkerJob::new_cmd( sender.input(Self::Input::DoInstall(
None, format!("https://github.com/WiVRn/WiVRn/releases/download/{tag}/WiVRn-standard-release.apk")
"adb".into(), ));
Some(vec![ }
"install".into(), Ok(WivrnApkRef::Commit(commit)) => {
wivrn_apk_path.to_string_lossy().to_string(), sender.input(Self::Input::DoInstall(
]), format!("https://github.com/WiVRn/WiVRn-APK/releases/download/apk-${commit}/org.meumeu.wivrn-standard-release.apk")
)); ));
let runner = JobWorker::new( }
jobs, };
sender.input_sender(), }
Box::new(move |e| match e { Self::Input::DoInstall(url) => {
JobWorkerOut::Log(log) => Self::Input::OnInstallRunnerLog(log), // TODO: we gonna cache or just download async every time?
JobWorkerOut::Exit(code) => Self::Input::OnInstallRunnerExit(code), match cache_file(&url, Some("apk")).await {
}), Err(e) => {
); eprintln!("Failed to download apk: {e}");
runner.start(); self.set_install_wivrn_status(InstallWivrnStatus::Done(Some(
self.install_runner = Some(runner); "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,
)
.await
{
Ok(out) if out.exit_code == 0 => {
InstallWivrnStatus::Success
}
Ok(out) => {
if out.stdout.contains(ADB_ERR_NO_DEV)
|| out.stderr.contains(ADB_ERR_NO_DEV)
{
InstallWivrnStatus::Done(Some(
concat!(
"No devices connected. Make sure your headset is connected ",
"to this computer and USB debugging is turned on."
)
.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)
))
}
}
Err(e) => {
eprintln!("Error: failed to run ADB: {e}");
InstallWivrnStatus::Done(Some(
"Failed to run ADB".into()
))
}
});
}
};
} }
Self::Input::UpdateSelectedProfile(p) => { Self::Input::UpdateSelectedProfile(p) => {
self.set_selected_profile(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)))
}
});
}
} }
} }
@ -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");