Skip to content

Commit 8eef5d2

Browse files
runner: Initial abstraction for Runners
1 parent 708d116 commit 8eef5d2

File tree

5 files changed

+197
-0
lines changed

5 files changed

+197
-0
lines changed

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mod error;
2+
pub mod runner;
23
pub use error::Error;
34

45
pub mod proto {

src/runner/mod.rs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
mod proton;
2+
mod umu;
3+
mod wine;
4+
5+
pub use proton::Proton;
6+
pub use umu::UMU;
7+
pub use wine::Wine;
8+
9+
use crate::Error;
10+
use std::{
11+
path::{Path, PathBuf},
12+
process::Command,
13+
};
14+
15+
/// Common information about a runner
16+
#[derive(Debug)]
17+
pub struct RunnerInfo {
18+
name: String,
19+
version: String,
20+
directory: PathBuf,
21+
}
22+
23+
impl RunnerInfo {
24+
// This fuction is only meant to be called by the runners themselves hence this is not public
25+
fn try_from(directory: &Path, executable: &Path) -> Result<Self, Box<dyn std::error::Error>> {
26+
if !directory.exists() {
27+
return Err(Box::new(std::io::Error::new(
28+
std::io::ErrorKind::NotFound,
29+
format!("'{}' does not exist", directory.display()),
30+
)));
31+
}
32+
let full_path = directory.join(executable);
33+
34+
if !full_path.exists() || !full_path.is_file() {
35+
return Err(Box::new(Error::Io(std::io::Error::new(
36+
std::io::ErrorKind::NotFound,
37+
format!(
38+
"Executable '{}' not found in directory '{}'",
39+
executable.display(),
40+
directory.display()
41+
),
42+
))));
43+
}
44+
45+
let name = directory
46+
.file_name()
47+
.and_then(|n| n.to_str())
48+
.map(|s| s.to_string())
49+
.unwrap_or_else(|| "unknown".to_string());
50+
51+
let version = Command::new(&full_path)
52+
.arg("--version")
53+
.output()
54+
.map(|output| {
55+
let ver = String::from_utf8_lossy(&output.stdout).to_string();
56+
if ver.is_empty() {
57+
name.clone()
58+
} else {
59+
ver
60+
}
61+
})
62+
.map_err(|e| Error::Io(e))?;
63+
64+
Ok(RunnerInfo {
65+
name,
66+
directory: directory.to_path_buf(),
67+
version,
68+
})
69+
}
70+
}
71+
72+
impl RunnerInfo {
73+
/// Name of the runner
74+
pub fn name(&self) -> &str {
75+
&self.name
76+
}
77+
78+
/// Version of the runner
79+
pub fn version(&self) -> &str {
80+
&self.version
81+
}
82+
83+
/// Directory where the runner is located
84+
pub fn directory(&self) -> &Path {
85+
&self.directory
86+
}
87+
}
88+
89+
pub trait Runner {
90+
const EXECUTABLE: &'static str;
91+
/// Get the common runner information
92+
fn info(&self) -> &RunnerInfo;
93+
94+
/// Check if the runner executable is available and functional
95+
fn is_available(&self) -> bool {
96+
let executable_path = self.info().directory().join(Self::EXECUTABLE);
97+
executable_path.exists() && executable_path.is_file()
98+
}
99+
}

src/runner/proton.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
use super::{Runner, RunnerInfo};
2+
use std::path::{Path, PathBuf};
3+
4+
// TODO: These need to be set to use proton outside steam
5+
// STEAM_COMPAT_DATA_PATH
6+
// STEAM_COMPAT_CLIENT_INSTALL_PATH
7+
#[derive(Debug)]
8+
pub struct Proton {
9+
info: RunnerInfo,
10+
}
11+
12+
impl TryFrom<&Path> for Proton {
13+
type Error = Box<dyn std::error::Error>;
14+
15+
fn try_from(path: &Path) -> Result<Self, Self::Error> {
16+
let executable = PathBuf::from(Self::EXECUTABLE);
17+
let info = RunnerInfo::try_from(path, &executable)?;
18+
Ok(Proton { info })
19+
}
20+
}
21+
22+
impl Runner for Proton {
23+
const EXECUTABLE: &'static str = "proton";
24+
fn info(&self) -> &RunnerInfo {
25+
&self.info
26+
}
27+
}

src/runner/umu.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use super::{Runner, RunnerInfo};
2+
use std::path::{Path, PathBuf};
3+
4+
#[derive(Debug)]
5+
pub struct UMU {
6+
info: RunnerInfo,
7+
proton_path: Option<PathBuf>,
8+
}
9+
10+
impl TryFrom<&Path> for UMU {
11+
type Error = Box<dyn std::error::Error>;
12+
13+
fn try_from(path: &Path) -> Result<Self, Self::Error> {
14+
let executable = PathBuf::from(Self::EXECUTABLE);
15+
let mut info = RunnerInfo::try_from(path, &executable)?;
16+
let pretty_version = info
17+
.version
18+
.split_whitespace()
19+
.nth(2)
20+
.unwrap_or("unknown")
21+
.to_string();
22+
info.version = pretty_version;
23+
Ok(UMU {
24+
info,
25+
proton_path: None,
26+
})
27+
}
28+
}
29+
30+
impl Runner for UMU {
31+
const EXECUTABLE: &'static str = "umu-run";
32+
fn info(&self) -> &RunnerInfo {
33+
&self.info
34+
}
35+
}

src/runner/wine.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use super::{Runner, RunnerInfo};
2+
use std::path::{Path, PathBuf};
3+
4+
#[derive(Debug)]
5+
pub struct Wine {
6+
info: RunnerInfo,
7+
}
8+
9+
pub enum PrefixArch {
10+
Win32,
11+
Win64,
12+
}
13+
14+
pub enum WindowsVersion {
15+
Win7,
16+
Win8,
17+
Win10,
18+
}
19+
20+
impl TryFrom<&Path> for Wine {
21+
type Error = Box<dyn std::error::Error>;
22+
23+
fn try_from(path: &Path) -> Result<Self, Self::Error> {
24+
let executable = PathBuf::from(Self::EXECUTABLE);
25+
let info = RunnerInfo::try_from(path, &executable)?;
26+
Ok(Wine { info })
27+
}
28+
}
29+
30+
impl Runner for Wine {
31+
const EXECUTABLE: &'static str = "bin/wine";
32+
fn info(&self) -> &RunnerInfo {
33+
&self.info
34+
}
35+
}

0 commit comments

Comments
 (0)