diff --git a/.gitignore b/.gitignore index d516459..826c9d6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target auth_key.txt -cache.json \ No newline at end of file +cache.json +config.toml \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 4cb14d4..c5a6c94 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a2e47a1fbe209ee101dd6d61285226744c6c8d3c21c8dc878ba6cb9f467f3a" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "aho-corasick" version = "0.7.15" @@ -29,6 +44,21 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[package]] +name = "backtrace" +version = "0.3.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4717cfcbfaa661a0fd48f8453951837ae7e8f81e481fbb136e3202d72805a744" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "base64" version = "0.13.0" @@ -133,6 +163,28 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "failure" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" +dependencies = [ + "backtrace", + "failure_derive", +] + +[[package]] +name = "failure_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "fnv" version = "1.0.7" @@ -221,6 +273,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "gimli" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4075386626662786ddb0ec9081e7c7eeb1ba31951f447ca780ef9f5d568189" + [[package]] name = "h2" version = "0.3.4" @@ -450,6 +508,16 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c835948974f68e0bd58636fc6c5b1fbff7b297e3046f11b3b3c18bbac012c6d" +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + [[package]] name = "mio" version = "0.7.13" @@ -572,6 +640,12 @@ dependencies = [ "libc", ] +[[package]] +name = "object" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5b3dd1c072ee7963717671d1ca129f1048fda25edea6b752bfc71ac8854170" + [[package]] name = "once_cell" version = "1.8.0" @@ -806,6 +880,12 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + [[package]] name = "ryu" version = "1.0.5" @@ -954,6 +1034,7 @@ name = "steam_shortcuts_sync" version = "0.1.0" dependencies = [ "config", + "failure", "reqwest", "serde 1.0.130", "serde_json", @@ -997,6 +1078,18 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "synstructure" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "474aaa926faa1603c40b7885a9eaea29b444d1cb2850cb7c0e37bb1a4182f4fa" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + [[package]] name = "tap" version = "1.0.1" diff --git a/Cargo.toml b/Cargo.toml index e871be7..c5bca86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,4 +12,5 @@ serde={version="*", features=["derive"]} serde_json="*" tokio = { version = "1.11.0", features = ["full"] } reqwest = { version = "*", features = ["default"] } -config = "*" \ No newline at end of file +config = "*" +failure = "*" \ No newline at end of file diff --git a/src/egs/get_manifests.rs b/src/egs/get_manifests.rs index 7d4d7cd..218623b 100644 --- a/src/egs/get_manifests.rs +++ b/src/egs/get_manifests.rs @@ -1,39 +1,101 @@ +use super::{EpicGamesLauncherSettings, ManifestItem}; use std::env::{self, VarError}; +use std::error::Error; use std::fs::{DirEntry, File}; use std::io::BufReader; use std::path::Path; -use std::error::Error; -use super::ManifestItem; -pub fn get_egs_manifests() -> Result, Box> { - let manifest_dir_path = get_manifest_dir_path()?; +use failure::*; + +#[derive(Debug, Fail)] +pub enum EpicGamesManifestsError { + #[fail(display = "Path to EpicGamesLauncher not defined, it must be defined on linux")] + PathNotDefined, + + #[fail( + display = "EpicGamesLauncher path: {} could not be found. Try to specify a different path for the EpicGamesLauncher.", + path + )] + PathNotFound { path: String }, + + #[fail( + display = "Could not read EpicGamesLauncher manifest directory at {} error: {}", + path, error + )] + ReadDirError { path: String, error: std::io::Error }, +} + +pub fn get_egs_manifests( + settings: &EpicGamesLauncherSettings, +) -> Result, EpicGamesManifestsError> { + use EpicGamesManifestsError::*; + + let manifest_dir_path = get_manifest_dir_path(settings)?; let manifest_dir_result = std::fs::read_dir(&manifest_dir_path); - if let Err(err) = manifest_dir_result { - println!("Could not find manifest directory: {}", manifest_dir_path); - return Result::Err(Box::new(err)); + + match manifest_dir_result { + Ok(manifest_dir) => { + let manifests = manifest_dir + .filter_map(|dir| dir.ok()) + .filter_map(get_manifest_item) + .filter(is_game_installed); + Ok(manifests.collect()) + } + Err(err) => Err(ReadDirError { + error: err, + path: manifest_dir_path, + }), } - let manifest_dir = manifest_dir_result?; - let manifests = manifest_dir - .filter_map(|dir| dir.ok()) - .filter_map(get_manifest_item) - .filter(is_game_installed); - Ok(manifests.collect()) } -fn get_manifest_dir_path() -> Result { - let key = "SYSTEMDRIVE"; - let system_drive = env::var(key)?; - Ok(format!( - "{system_drive}//ProgramData//Epic//EpicGamesLauncher//Data//Manifests", - system_drive = system_drive - )) +fn get_manifest_dir_path( + settings: &EpicGamesLauncherSettings, +) -> Result { + use EpicGamesManifestsError::*; + if let Some(location) = &settings.location { + let path = Path::new(location).join("Data").join("Manifests"); + if path.exists() { + return Ok(path.to_str().unwrap().to_string()); + } else { + return Err(PathNotFound { + path: path.to_str().unwrap().to_string(), + }); + } + } else { + #[cfg(target_os = "linux")] + { + //No path defined for epic gamestore, and we cannot guess on linux + return Err(PathNotDefined); + } + + #[cfg(target_os = "windows")] + { + let key = "SYSTEMDRIVE"; + let system_drive = + env::var(key).expect("We are on windows, we must know what the SYSTEMDRIVE is"); + + let path = Path::new(system_drive.as_str()) + .join("ProgramData") + .join("Epic") + .join("EpicGamesLauncher") + .join("Data") + .join("Manifests"); + if path.exists() { + return Ok(path.to_str().unwrap().to_string()); + } else { + return Err(PathNotFound { + path: path.to_str().unwrap().to_string(), + }); + } + } + } } - fn is_game_installed(manifest:&ManifestItem) -> bool{ - Path::new(manifest.manifest_location.as_str()).exists() +fn is_game_installed(manifest: &ManifestItem) -> bool { + Path::new(manifest.manifest_location.as_str()).exists() } - fn get_manifest_item(dir_entry: DirEntry) -> Option { +fn get_manifest_item(dir_entry: DirEntry) -> Option { if let Some(extension) = dir_entry.path().extension() { if extension.eq("item") { if let Ok(file) = File::open(dir_entry.path()) { diff --git a/src/egs/mod.rs b/src/egs/mod.rs index 30bc729..1a33a3a 100644 --- a/src/egs/mod.rs +++ b/src/egs/mod.rs @@ -2,4 +2,5 @@ mod get_manifests; mod manifest_item; mod settings; pub use manifest_item::*; -pub use get_manifests::get_egs_manifests; \ No newline at end of file +pub use get_manifests::get_egs_manifests; +pub use settings::EpicGamesLauncherSettings; \ No newline at end of file diff --git a/src/egs/settings.rs b/src/egs/settings.rs index 98303a9..0e762cb 100644 --- a/src/egs/settings.rs +++ b/src/egs/settings.rs @@ -1,12 +1,12 @@ use serde::Deserialize; #[derive(Debug, Deserialize)] -struct EpicGamesLauncher { - enabled: bool, - location: Option, +pub struct EpicGamesLauncherSettings { + pub enabled: bool, + pub location: Option, } -impl Default for EpicGamesLauncher { +impl Default for EpicGamesLauncherSettings { fn default() -> Self { // On windows @@ -16,11 +16,11 @@ impl Default for EpicGamesLauncher { // On linux - // We can notguess, so lets not enable by default + // We can not guess, so lets not enable by default #[cfg(target_os = "linux")] let enabled = false; - EpicGamesLauncher { + EpicGamesLauncherSettings { enabled, location: None, } diff --git a/src/legendary/mod.rs b/src/legendary/mod.rs index d494711..c0eb0a3 100644 --- a/src/legendary/mod.rs +++ b/src/legendary/mod.rs @@ -1,5 +1,7 @@ mod legendary_game; mod get_legendary_games; +mod settings; pub use get_legendary_games::*; -pub use legendary_game::*; \ No newline at end of file +pub use legendary_game::*; +pub use settings::*; \ No newline at end of file diff --git a/src/legendary/settings.rs b/src/legendary/settings.rs new file mode 100644 index 0000000..96ea0fe --- /dev/null +++ b/src/legendary/settings.rs @@ -0,0 +1,16 @@ +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +pub struct LegendarySettings { + pub enabled: bool, + pub executable: Option, +} + +impl Default for LegendarySettings { + fn default() -> Self { + Self { + enabled: true, + executable: None, + } + } +} diff --git a/src/main.rs b/src/main.rs index 905dff3..b2f4484 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,9 +9,11 @@ use std::{ mod cached_search; mod egs; mod legendary; +mod settings; mod steamgriddb; +mod platform; -use crate::legendary::get_legendary_games; +use crate::{legendary::get_legendary_games, settings::Settings}; use egs::{get_egs_manifests, ManifestItem}; use std::error::Error; use steam_shortcuts_util::{ @@ -60,35 +62,35 @@ fn get_shortcuts_for_user(user: &SteamUsersInfo) -> ShortcutInfo { #[tokio::main] async fn main() -> Result<(), Box> { - if !Path::new("auth_key.txt").exists() { - println!("File auth_key.txt not found, the file has been created, please get a key from https://www.steamgriddb.com/ and copy it into auth_key.txt"); - File::create(Path::new("auth_key.txt"))?; - return Ok(()); - } - - let auth_key = std::fs::read_to_string("auth_key.txt")?; - - if auth_key.trim().is_empty() { - println!("File auth_key.txt is empty please get an API key from https://www.steamgriddb.com/ and paste it into auth_key.txt"); + let settings = Settings::new()?; + + + let auth_key = settings.steamgrid_db.auth_key; + if settings.steamgrid_db.enabled && auth_key.is_none() { + println!("auth_key not found, please add it to the steamgrid_db settings "); return Ok(()); } + let auth_key = auth_key.unwrap(); + let client = steamgriddb_api::Client::new(auth_key); let mut search = CachedSearch::new(&client); - #[cfg(target_os = "windows")] - let egs_shortcuts = { - let egs_manifests = match get_egs_manifests() { - Ok(manifests) => manifests, - Err(e) => { - println!("Error getting manifests: {}", e); - vec![] - } + if settings.epic_games.enabled { + let egs_shortcuts = { + let egs_manifests = match get_egs_manifests(&settings.epic_games) { + Ok(manifests) => manifests, + Err(e) => { + println!("Error getting manifests for Epic Games Store: {}", e); + vec![] + } + }; + let egs_shortcuts: Vec = + egs_manifests.iter().map(|f| f.into()).collect(); + println!("Found {} installed EGS Games", egs_manifests.len()); + egs_shortcuts }; - let egs_shortcuts: Vec = egs_manifests.iter().map(|f| f.into()).collect(); - println!("Found {} installed EGS Games", egs_manifests.len()); - egs_shortcuts - }; + } #[cfg(target_os = "linux")] let legendary_shortcuts = { diff --git a/src/platform.rs b/src/platform.rs new file mode 100644 index 0000000..62bc471 --- /dev/null +++ b/src/platform.rs @@ -0,0 +1,13 @@ +use crate::{egs::EpicGamesLauncherSettings, legendary::LegendarySettings}; + +pub enum Platform{ + EpicGames{ + settings: EpicGamesLauncherSettings, + }, + Legendary{ + settings: LegendarySettings + } +} + + + diff --git a/src/settings.rs b/src/settings.rs index 5f0ce22..fcf7603 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,18 +1,68 @@ -use std::env; -use config::{ConfigError, Config, File, Environment}; +use crate::{ + egs::EpicGamesLauncherSettings, legendary::LegendarySettings, steamgriddb::SteamGridDbSettings, +}; +use config::{Config, ConfigError, Environment, File}; +use serde::Deserialize; +use std::{env, path::Path}; -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Default)] pub struct Settings { - debug: bool, - database: EpicGamesLauncher, + pub debug: bool, + pub epic_games: EpicGamesLauncherSettings, + pub legendary: LegendarySettings, + pub steamgrid_db: SteamGridDbSettings, +} + +#[derive(Debug, Deserialize, Default)] +struct SettingsOptional { + pub debug: Option, + pub epic_games: Option, + pub legendary: Option, + pub steamgrid_db: Option, +} + +impl From for Settings { + fn from(optional: SettingsOptional) -> Self { + Self { + debug: optional.debug.unwrap_or(false), + epic_games: optional.epic_games.unwrap_or(Default::default()), + legendary: optional.legendary.unwrap_or(Default::default()), + steamgrid_db: optional.steamgrid_db.unwrap_or(Default::default()), + } + } } //https://github.com/JosefNemec/Playnite/tree/master/source/Plugins/OriginLibrary //https://github.com/JosefNemec/Playnite/blob/master/source/Plugins/OriginLibrary/Origin.cs#L109 impl Settings { - pub fn new() -> Result { + let default = Self::default(); + if !Path::new("config.toml").exists() { + return Ok(default); + } + let mut s = Config::new(); + // Start off by merging in the "default" configuration file + s.merge(File::with_name("config.toml").required(false))?; + + // Add in the current environment file + // Default to 'development' env + // Note that this file is _optional_ + let env = env::var("RUN_MODE").unwrap_or_else(|_| "development".into()); + s.merge(File::with_name(&format!("config/{}", env)).required(false))?; + + // Add in a local configuration file + // This file shouldn't be checked in to git + s.merge(File::with_name("local.toml").required(false))?; + + // Add in settings from the environment (with a prefix of STEAM_SYNC) + // Eg.. `STEAM_SYNC_DEBUG=1 ./target/app` would set the `debug` key + s.merge(Environment::with_prefix("steam_sync"))?; + + // You can deserialize (and thus freeze) the entire configuration as + let optionals: SettingsOptional = s.try_into()?; + + Ok(optionals.into()) } -} \ No newline at end of file +} diff --git a/src/steamgriddb/mod.rs b/src/steamgriddb/mod.rs index 1bd4528..abfb15a 100644 --- a/src/steamgriddb/mod.rs +++ b/src/steamgriddb/mod.rs @@ -1,2 +1,2 @@ mod settings; -pub use settings::*; \ No newline at end of file +pub use settings::SteamGridDbSettings; \ No newline at end of file diff --git a/src/steamgriddb/settings.rs b/src/steamgriddb/settings.rs index 58950b7..57bd082 100644 --- a/src/steamgriddb/settings.rs +++ b/src/steamgriddb/settings.rs @@ -1,12 +1,12 @@ use serde::Deserialize; #[derive(Debug, Deserialize)] -pub struct SteamGridDb { +pub struct SteamGridDbSettings { pub enabled: bool, pub auth_key: Option, } -impl Default for SteamGridDb { +impl Default for SteamGridDbSettings { fn default() -> Self { Self { enabled: true,