mirror of
https://github.com/Drop-OSS/drop.git
synced 2026-07-24 17:03:00 +10:00
Async downloader, better Proton support (#183)
* feat: async downloader + other fixes * feat: windows command parsing + use library path for install path * feat: better proton support * feat: style fixes and store button now uses in-app * feat: emulator rename + umu emulator fix * feat: bring process creation inline with docs * fix: clippy
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
// Linux-only file
|
||||
|
||||
use std::{
|
||||
fs::{DirEntry, read_dir, read_to_string},
|
||||
io,
|
||||
path::PathBuf,
|
||||
sync::LazyLock,
|
||||
};
|
||||
|
||||
use database::{borrow_db_checked, borrow_db_mut_checked};
|
||||
use log::warn;
|
||||
use serde::Serialize;
|
||||
|
||||
static SEARCH_PATHS: LazyLock<Vec<String>> = LazyLock::new(|| {
|
||||
let mut paths = vec!["/usr/share/steam/compatibilitytools.d/".to_owned()];
|
||||
|
||||
if let Some(home_dir) = std::env::home_dir() {
|
||||
paths.push(
|
||||
home_dir
|
||||
.join(".steam/root/compatibilitytools.d/")
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
paths
|
||||
});
|
||||
|
||||
pub fn read_proton_path(proton_path: PathBuf) -> Result<Option<ProtonPath>, io::Error> {
|
||||
let read_dir = read_dir(&proton_path)?
|
||||
.flatten()
|
||||
.collect::<Vec<DirEntry>>();
|
||||
let has_proton_path = read_dir
|
||||
.iter()
|
||||
.find(|v| v.file_name().to_string_lossy() == "proton")
|
||||
.is_some();
|
||||
if !has_proton_path {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let compat_vdf = read_dir
|
||||
.iter()
|
||||
.find(|v| v.file_name().to_string_lossy() == "compatibilitytool.vdf");
|
||||
|
||||
let compat_vdf = match compat_vdf {
|
||||
Some(v) => v,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let compat_vdf = read_to_string(compat_vdf.path())?;
|
||||
let compat_vdf = keyvalues_parser::parse(&compat_vdf)
|
||||
.inspect_err(|err| warn!("failed to parse vdf: {:?}", err))
|
||||
.map_err(|err| io::Error::other(err.to_string()))?;
|
||||
|
||||
// Function was made with a lot of trial and error
|
||||
// Not intended to be readable
|
||||
let get_display_name = || -> Option<String> {
|
||||
let compat_tools = compat_vdf.value.unwrap_obj();
|
||||
let compat_tools = compat_tools.values().next()?.iter().next()?;
|
||||
let compat_tools = compat_tools.get_obj().unwrap();
|
||||
let compat_tools = compat_tools.values().next()?.iter().next()?.get_obj()?;
|
||||
let display_name = compat_tools.get("display_name")?.iter().next()?.get_str()?;
|
||||
Some(display_name.to_string())
|
||||
};
|
||||
|
||||
if let Some(display_name) = get_display_name() {
|
||||
return Ok(Some(ProtonPath {
|
||||
path: proton_path.to_string_lossy().to_string(),
|
||||
name: display_name,
|
||||
}));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn discover_proton_paths() -> Result<Vec<ProtonPath>, io::Error> {
|
||||
let mut results = Vec::new();
|
||||
|
||||
for search_path in &*SEARCH_PATHS {
|
||||
if let Ok(potential_dirs) = read_dir(search_path) {
|
||||
for proton_path in potential_dirs {
|
||||
if let Some(proton) = read_proton_path(proton_path?.path())? {
|
||||
results.push(proton);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct ProtonPath {
|
||||
pub path: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct ProtonPaths {
|
||||
pub autodiscovered: Vec<ProtonPath>,
|
||||
pub custom: Vec<ProtonPath>,
|
||||
pub default: Option<String>,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn fetch_proton_paths() -> Result<ProtonPaths, String> {
|
||||
let autodiscovered = discover_proton_paths().map_err(|v| v.to_string())?;
|
||||
|
||||
let db_lock = borrow_db_checked();
|
||||
|
||||
let custom = db_lock
|
||||
.applications
|
||||
.additional_proton_paths
|
||||
.iter()
|
||||
.flat_map(|v| read_proton_path(PathBuf::from(v)))
|
||||
.flatten()
|
||||
.collect::<Vec<ProtonPath>>();
|
||||
|
||||
let default = db_lock.applications.default_proton_path.clone();
|
||||
|
||||
Ok(ProtonPaths {
|
||||
autodiscovered,
|
||||
custom,
|
||||
default,
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn add_proton_layer(path: String) -> Result<(), String> {
|
||||
let path = PathBuf::from(path);
|
||||
|
||||
let proton_layer = read_proton_path(path)
|
||||
.map_err(|err| err.to_string())?
|
||||
.ok_or("Unable to detect Proton at selected path.".to_owned())?;
|
||||
|
||||
let mut db = borrow_db_mut_checked();
|
||||
db.applications
|
||||
.additional_proton_paths
|
||||
.push(proton_layer.path);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn remove_proton_layer(index: usize) {
|
||||
let mut db = borrow_db_mut_checked();
|
||||
let deleted = db.applications.additional_proton_paths.try_remove(index);
|
||||
if let Some(deleted) = deleted
|
||||
&& let Some(default_path) = &db.applications.default_proton_path
|
||||
&& *default_path == deleted {
|
||||
db.applications.default_proton_path = None;
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn set_default(path: String) -> Result<(), String> {
|
||||
let proton_paths = fetch_proton_paths().await?;
|
||||
|
||||
let valid = proton_paths
|
||||
.autodiscovered
|
||||
.iter()
|
||||
.find(|v| v.path == path)
|
||||
.or(proton_paths.custom.iter().find(|v| v.path == path))
|
||||
.is_some();
|
||||
|
||||
if !valid {
|
||||
return Err("Invalid default Proton path.".to_string());
|
||||
}
|
||||
|
||||
let mut db_lock = borrow_db_mut_checked();
|
||||
db_lock.applications.default_proton_path = Some(path);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user