Skip to content

Commit

Permalink
feat: convert everything to be async
Browse files Browse the repository at this point in the history
- added support for gpu driver specific tweaks
  • Loading branch information
cecilia-sanare committed Mar 14, 2024
1 parent 2603db9 commit 36efb19
Show file tree
Hide file tree
Showing 15 changed files with 1,061 additions and 256 deletions.
932 changes: 919 additions & 13 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
async-process = "2.1.0"
clap = { version = "4.5.1", features = ["derive"] }
futures = "0.3.30"
home = "0.5.9"
keyvalues-parser = "0.2.0"
keyvalues-serde = "0.2.1"
Expand All @@ -18,13 +20,14 @@ notify = "6.1.1"
notify-debouncer-full = "0.3.1"
owo-colors = "4.0.0"
pretty_env_logger = "0.5.0"
protontweaks-api = { git = "https://github.com/rain-cafe/protontweaks-api-rs", version = "0.1.0", rev = "c2d6fa41c0488e00e96578f99d34f21af8418127" }
regex = "1.10.3"
reqwest = { version = "0.11.24", features = ["blocking", "json"] }
rust_search = "2.1.0"
serde = { version = "1.0.197", features = ["derive"] }
serde_json = "1.0.114"
sha256 = "1.5.0"
shlex = "1.3.0"
tokio = { version = "1.36.0", features = ["rt-multi-thread", "macros"] }

[build-dependencies]
chrono = "0.4.34"
4 changes: 4 additions & 0 deletions nix/pkgs/protontweaks.nix
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ rustPlatform.buildRustPackage {

cargoLock = {
lockFile = ../../Cargo.lock;
# Temporary until we deploy protontweaks-api-rs to crates.io
outputHashes = {
"protontweaks-api-0.1.0" = "sha256-DqqJgCi+0WSvuypZ3cbjsuc10iXeSwqah/J+f3H5vtM=";
};
};

# Most tests fail due to the isolation
Expand Down
1 change: 1 addition & 0 deletions shell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ pkgs.mkShell {
RUST_LOG = "trace";

LD_LIBRARY_PATH = "$LD_LIBRARY_PATH:${ with pkgs; lib.makeLibraryPath [
vulkan-loader
xorg.libX11
xorg.libXcursor
xorg.libXrandr
Expand Down
167 changes: 10 additions & 157 deletions src/apps/mod.rs
Original file line number Diff line number Diff line change
@@ -1,182 +1,35 @@
use std::collections::HashMap;

use reqwest::{StatusCode, Url};
use serde::Deserialize;
use protontweaks_api::app::App;

use crate::utils::protontricks;

const PROTONTWEAKS_DB: &str = env!("PROTONTWEAKS_DB");

#[derive(Debug, Deserialize)]
pub struct App {
pub id: String,
pub name: String,
pub tweaks: Tweaks,
pub issues: Vec<Issue>,
}

#[derive(Debug, Deserialize)]
pub struct Tweaks {
pub tricks: Vec<String>,
pub env: HashMap<String, String>,
pub settings: TweakSettings,
}

#[derive(Debug, Deserialize)]
pub struct TweakSettings {
pub gamemode: Option<bool>,
pub mangohud: Option<bool>,
}

#[derive(Debug, Deserialize)]
pub struct Issue {
pub description: String,
pub solution: String,
}

#[derive(Debug, Deserialize)]
pub struct AppsList {
pub sha: String,
pub short_sha: String,
pub apps: Vec<MiniApp>,
}

#[derive(Debug, Deserialize)]
pub struct MiniApp {
pub id: String,
pub name: String,
}

pub fn url(path: &str) -> Url {
let url = Url::parse(PROTONTWEAKS_DB).unwrap();

return url.join(&format!("{path}.json")).unwrap();
}

pub fn apps_list() -> AppsList {
let url = url("apps");

debug!("Requesting apps from '{url}'...");

let response = reqwest::blocking::get(url).unwrap();

trace!("Apps received!");

response.json::<AppsList>().unwrap()
}

pub fn apps() -> Vec<MiniApp> {
apps_list().apps
}

pub fn list_ids() -> Vec<String> {
apps_list().apps.iter().map(|x| x.id.to_owned()).collect()
}

pub fn try_get(app_id: &str) -> Result<App, String> {
let url = url(app_id);

debug!("Requesting file from '{url}'...");

let response = reqwest::blocking::get(url).map_err(|e| {
warn!("{}", e.to_string());
e.to_string()
})?;

if response.status() == StatusCode::NOT_FOUND {
let message = format!("App {app_id} does not exist!");
info!("{message}");
return Err(message);
}

trace!("App received!");

let mut app = response
.json::<App>()
.map_err(|_| "Failed to parse app info".to_string())?;

app.id = app_id.to_string();

Ok(app)
}

pub fn get(app_id: &str) -> App {
try_get(app_id).unwrap()
}

/// Applies the app and returns a result of whether it was successful
pub fn try_apply(app: &App) -> Result<(u32, u32), String> {
pub async fn try_apply(app: &App) -> Result<(u32, u32), String> {
let tweaks = app.flatten().await;
trace!("App ID: {}; Name: {}", app.id, app.name);

if app.tweaks.tricks.len() == 0 {
if tweaks.tricks.len() == 0 {
warn!("No tricks were found for {} -> {}", app.id, app.name);
return Ok((0, 0));
}

trace!("Installing tricks for {} -> {}", app.id, app.name);
let tweaks_applied = protontricks::install::components(&app.id, &app.tweaks.tricks)?;
let tweaks_applied = protontricks::install::components(&app.id, &tweaks.tricks).await?;

return Ok((tweaks_applied, app.tweaks.tricks.len().try_into().unwrap()));
return Ok((tweaks_applied, tweaks.tricks.len().try_into().unwrap()));
}

/// Applies the app and panics if a failure occurs.
pub fn apply(app: &App) -> (u32, u32) {
return try_apply(app).unwrap();
pub async fn apply(app: &App) -> (u32, u32) {
return try_apply(app).await.unwrap();
}

/// Applies the app, if an error occurs it simply logs it and returns that no tweaks were applied
pub fn apply_safe(app: &App) -> (u32, u32) {
match try_apply(app) {
pub async fn apply_safe(app: &App) -> (u32, u32) {
match try_apply(app).await {
Ok(result) => result,
Err(e) => {
error!("{e}");
(0, app.tweaks.tricks.len().try_into().unwrap())
}
}
}

#[cfg(test)]
mod tests {
use crate::apps::{apps_list, get, list_ids, url};

#[test]
fn url_should_create_a_apps_url() {
assert_eq!(
url("apps").to_string(),
"https://api.protontweaks.com/apps.json"
);
}

#[test]
fn apps_list_should_return_the_tweaks_list() {
let apps_list = apps_list();

assert!(
apps_list.apps.len() > 0,
"Expected to receive a list of valid tweaked apps!"
);
}

#[test]
fn list_ids_should_return_the_tweaks_list() {
let ids = list_ids();

assert!(
ids.len() > 0,
"Expected to receive a list of valid tweaked apps!"
);
}

#[test]
fn get_should_return_the_app_info() {
let expected_id = "644930";
let app = get(expected_id);

assert_eq!(app.id, expected_id);
assert_eq!(app.issues.len(), 1);
assert_eq!(app.tweaks.tricks.len(), 1);
assert_eq!(app.tweaks.env.len(), 0);
assert_eq!(app.tweaks.settings.gamemode, None);
assert_eq!(app.tweaks.settings.mangohud, None);
}
}
20 changes: 9 additions & 11 deletions src/commands/list.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
use crate::{
apps::{self, MiniApp},
utils::protontricks,
};
use futures::future;
use protontweaks_api::Protontweaks;

pub fn command() -> Result<(), String> {
println!("Fetching installed apps...");
use crate::utils::protontricks;

let installed_app_ids = protontricks::list::apps()?;
pub async fn command() -> Result<(), String> {
println!("Fetching installed apps...");
let api = Protontweaks::new();

let apps = apps::apps();
let (installed_app_ids, apps) = future::join(protontricks::list::apps(), api.apps()).await;

let apps: Vec<&MiniApp> = apps
let apps = apps
.iter()
.filter(|app| installed_app_ids.contains(&app.id))
.collect();
.filter(|app| installed_app_ids.contains(&app.id));

for app in apps {
println!("{0} ({1})", app.name, app.id);
Expand Down
43 changes: 25 additions & 18 deletions src/commands/run.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
use owo_colors::OwoColorize;
use protontweaks_api::{app::App, Protontweaks};
use std::{collections::HashMap, process::Command};

use clap::Args;
use regex::Regex;

use crate::{
apps::{self, App},
utils::command,
};
use crate::{apps, utils::command};

#[derive(Debug, Args)]
pub struct CommandArgs {
/// The steam launch command '%command%'
pub command_args: Option<Vec<String>>,
}

pub fn command(args: CommandArgs) -> Result<(), String> {
let (command, args, app) = parse_command(args)?;
pub async fn command(args: CommandArgs) -> Result<(), String> {
let (command, args, app) = parse_command(args).await?;

if let Some(app) = &app {
let (app_tweaks_applied, app_total_tweaks) = apps::apply_safe(app);
let tweaks = if let Some(app) = &app {
let (app_tweaks_applied, app_total_tweaks) = apps::apply_safe(app).await;

if app_tweaks_applied == 0 {
println!(
Expand All @@ -33,9 +31,13 @@ pub fn command(args: CommandArgs) -> Result<(), String> {
format!("{app_tweaks_applied} tweaks").bold()
);
}
}

let env = &app.map_or(HashMap::new(), |app| app.tweaks.env);
Some(app.flatten().await)
} else {
None
};

let env = &tweaks.map_or(HashMap::new(), |tweaks| tweaks.env);

Command::new(command)
.args(args)
Expand All @@ -48,7 +50,7 @@ pub fn command(args: CommandArgs) -> Result<(), String> {
Ok(())
}

fn parse_command(args: CommandArgs) -> Result<(String, Vec<String>, Option<App>), String> {
async fn parse_command(args: CommandArgs) -> Result<(String, Vec<String>, Option<App>), String> {
let command_args = args.command_args.unwrap();
let is_proton = command_args
.iter()
Expand All @@ -65,7 +67,9 @@ fn parse_command(args: CommandArgs) -> Result<(String, Vec<String>, Option<App>)

println!("App ID: {0}", &caps["app_id"]);

apps::try_get(app_id).ok()
let api = Protontweaks::new();

api.try_app(app_id).await.ok()
} else {
warn!("Unable to detect App ID, acting purely as a passthrough...");
None
Expand All @@ -83,32 +87,34 @@ fn parse_command(args: CommandArgs) -> Result<(String, Vec<String>, Option<App>)
pub mod tests {
use super::{parse_command, CommandArgs};

#[test]
pub fn parse_command_should_support_simple_commands() {
#[tokio::test]
pub async fn parse_command_should_support_simple_commands() {
let (command, args, app) = parse_command(CommandArgs {
command_args: Some(vec!["echo".to_string(), "hello".to_string()]),
})
.await
.expect("Failed to parse command.");

assert_eq!(command, "echo");
assert_eq!(args, vec!["hello"]);
assert!(app.is_none(), "Expected app to not be defined!");
}

#[test]
pub fn parse_command_should_support_unified_commands() {
#[tokio::test]
pub async fn parse_command_should_support_unified_commands() {
let (command, args, app) = parse_command(CommandArgs {
command_args: Some(vec!["echo hello".to_string()]),
})
.await
.expect("Failed to execute command.");

assert_eq!(command, "echo");
assert_eq!(args, vec!["hello"]);
assert!(app.is_none(), "Expected app to not be defined!");
}

#[test]
pub fn parse_command_should_support_steam_launch_commands() {
#[tokio::test]
pub async fn parse_command_should_support_steam_launch_commands() {
let command_args = vec![
"~/.local/share/Steam/ubuntu12_32/reaper",
"SteamLaunch",
Expand All @@ -127,6 +133,7 @@ pub mod tests {
let (command, args, app) = parse_command(CommandArgs {
command_args: Some(command_args),
})
.await
.expect("Failed to execute command.");

assert_eq!(command, "~/.local/share/Steam/ubuntu12_32/reaper");
Expand Down
Loading

0 comments on commit 36efb19

Please sign in to comment.