diff --git a/.gitignore b/.gitignore index 826c9d6..ab24616 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /target auth_key.txt cache.json -config.toml \ No newline at end of file +config.toml +.thumbnails \ No newline at end of file diff --git a/biglogo.png b/biglogo.png new file mode 100644 index 0000000..f4c5622 Binary files /dev/null and b/biglogo.png differ diff --git a/build.rs b/build.rs index 8e928dc..9a9bcd4 100644 --- a/build.rs +++ b/build.rs @@ -1,11 +1,13 @@ use std::io; -#[cfg(windows)] use winres::WindowsResource; +#[cfg(windows)] +use winres::WindowsResource; fn main() -> io::Result<()> { - #[cfg(windows)] { + #[cfg(windows)] + { WindowsResource::new() .set_icon("resources/logo.ico") .compile()?; } Ok(()) -} \ No newline at end of file +} diff --git a/src/amazon/amazon_game.rs b/src/amazon/amazon_game.rs index 11a59c0..b76880a 100644 --- a/src/amazon/amazon_game.rs +++ b/src/amazon/amazon_game.rs @@ -1,25 +1,14 @@ use steam_shortcuts_util::{shortcut::ShortcutOwned, Shortcut}; #[derive(Debug, Clone)] -pub struct AmazonGame{ - pub title : String, - pub id : String +pub struct AmazonGame { + pub title: String, + pub id: String, } - - impl From for ShortcutOwned { fn from(game: AmazonGame) -> Self { let launch = format!("amazon-games://play/{}", game.id); - Shortcut::new( - "0", - game.title.as_str(), - launch.as_str(), - "", - "", - "", - "", - ) - .to_owned() + Shortcut::new("0", game.title.as_str(), launch.as_str(), "", "", "", "").to_owned() } } diff --git a/src/amazon/amazon_platform.rs b/src/amazon/amazon_platform.rs index 161905c..59b86ec 100644 --- a/src/amazon/amazon_platform.rs +++ b/src/amazon/amazon_platform.rs @@ -1,23 +1,25 @@ -use std::{error::Error, path::{PathBuf, Path}}; +use std::{ + error::Error, + path::{Path, PathBuf}, +}; use sqlite::State; use crate::platform::Platform; -use super::{AmazonSettings, AmazonGame}; +use super::{AmazonGame, AmazonSettings}; - -pub struct AmazonPlatform{ - pub settings:AmazonSettings +pub struct AmazonPlatform { + pub settings: AmazonSettings, } -impl Platform> for AmazonPlatform{ +impl Platform> for AmazonPlatform { #[cfg(windows)] - fn enabled(&self) -> bool { + fn enabled(&self) -> bool { self.settings.enabled } - - #[cfg(target_family="unix")] + + #[cfg(target_family = "unix")] fn enabled(&self) -> bool { false } @@ -27,14 +29,16 @@ impl Platform> for AmazonPlatform{ } fn get_shortcuts(&self) -> Result, Box> { - let sqllite_path = get_sqlite_path().expect("This should enver get called if settings are invalid"); + let sqllite_path = + get_sqlite_path().expect("This should enver get called if settings are invalid"); let mut result = vec![]; let connection = sqlite::open(sqllite_path)?; - let mut statement = connection.prepare("SELECT Id, ProductTitle FROM DbSet WHERE Installed = 1")?; + let mut statement = + connection.prepare("SELECT Id, ProductTitle FROM DbSet WHERE Installed = 1")?; while let State::Row = statement.next().unwrap() { let id = statement.read::(0); let title = statement.read::(1); - if let (Ok(id),Ok(title)) = (id,title){ + if let (Ok(id), Ok(title)) = (id, title) { result.push(AmazonGame { title, id }); } } @@ -43,15 +47,16 @@ impl Platform> for AmazonPlatform{ fn settings_valid(&self) -> crate::platform::SettingsValidity { let path = get_sqlite_path(); - if path.is_some(){ + if path.is_some() { crate::platform::SettingsValidity::Valid - }else{ - crate::platform::SettingsValidity::Invalid { reason: format!("Could not find Amazon Games installation")} + } else { + crate::platform::SettingsValidity::Invalid { + reason: "Could not find Amazon Games installation".to_string(), + } } - } - #[cfg(target_family ="unix")] + #[cfg(target_family = "unix")] fn create_symlinks(&self) -> bool { false } @@ -61,17 +66,21 @@ impl Platform> for AmazonPlatform{ } } - fn get_sqlite_path() -> Option { match std::env::var("LOCALAPPDATA") { - Ok(localdata) => { - let path = Path::new(&localdata).join("Amazon Games").join("Data").join("Games").join("Sql").join("GameInstallInfo.sqlite"); + Ok(localdata) => { + let path = Path::new(&localdata) + .join("Amazon Games") + .join("Data") + .join("Games") + .join("Sql") + .join("GameInstallInfo.sqlite"); if path.exists() { Some(path) - }else{ + } else { None } } Err(_e) => None, } -} \ No newline at end of file +} diff --git a/src/amazon/amazon_settings.rs b/src/amazon/amazon_settings.rs index a7a7563..b5d6745 100644 --- a/src/amazon/amazon_settings.rs +++ b/src/amazon/amazon_settings.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Deserialize, Serialize, Clone)] -pub struct AmazonSettings{ - pub enabled:bool -} \ No newline at end of file +pub struct AmazonSettings { + pub enabled: bool, +} diff --git a/src/amazon/mod.rs b/src/amazon/mod.rs index 8795361..afbe9d9 100644 --- a/src/amazon/mod.rs +++ b/src/amazon/mod.rs @@ -5,4 +5,4 @@ pub mod amazon_game; pub use amazon_game::*; pub mod amazon_settings; -pub use amazon_settings::*; \ No newline at end of file +pub use amazon_settings::*; diff --git a/src/egs/epic_platform.rs b/src/egs/epic_platform.rs index af763f4..bb64fc8 100644 --- a/src/egs/epic_platform.rs +++ b/src/egs/epic_platform.rs @@ -11,7 +11,9 @@ pub struct EpicPlatform { impl EpicPlatform { pub fn new(settings: &EpicGamesLauncherSettings) -> Self { - EpicPlatform { settings: settings.clone() } + EpicPlatform { + settings: settings.clone(), + } } } diff --git a/src/egs/get_manifests.rs b/src/egs/get_manifests.rs index f8833db..e78d2e4 100644 --- a/src/egs/get_manifests.rs +++ b/src/egs/get_manifests.rs @@ -40,12 +40,24 @@ pub(crate) fn get_egs_manifests( .filter_map(|dir| dir.ok()) .filter_map(get_manifest_item) .filter(is_game_installed) - .filter(is_game_launchable); - let mut manifests : Vec = manifests.collect(); - manifests.sort_by_key(|m| format!("{}-{}-{}",m.install_location,m.launch_executable,m.is_managed)); - manifests.dedup_by_key(|m| format!("{}-{}-{}",m.install_location,m.launch_executable,m.is_managed)); - for mut manifest in &mut manifests{ - if settings.safe_launch.contains(&manifest.display_name) || settings.safe_launch.contains(&manifest.get_key()){ + .filter(is_game_launchable); + let mut manifests: Vec = manifests.collect(); + manifests.sort_by_key(|m| { + format!( + "{}-{}-{}", + m.install_location, m.launch_executable, m.is_managed + ) + }); + manifests.dedup_by_key(|m| { + format!( + "{}-{}-{}", + m.install_location, m.launch_executable, m.is_managed + ) + }); + for mut manifest in &mut manifests { + if settings.safe_launch.contains(&manifest.display_name) + || settings.safe_launch.contains(&manifest.get_key()) + { manifest.safe_launch = true; } } @@ -58,7 +70,6 @@ pub(crate) fn get_egs_manifests( } } - fn get_manifest_dir_path( settings: &EpicGamesLauncherSettings, ) -> Result { @@ -113,7 +124,7 @@ fn location_from_registry() -> Option { if let Ok(path_string) = path_string { let path = Path::new(&path_string).join("Manifests"); if path.exists() { - return Some(path.to_path_buf()); + return Some(path); } } } @@ -132,7 +143,7 @@ fn guess_default_location() -> PathBuf { .join("EpicGamesLauncher") .join("Data") .join("Manifests"); - path.to_path_buf() + path } fn is_game_installed(manifest: &ManifestItem) -> bool { diff --git a/src/egs/manifest_item.rs b/src/egs/manifest_item.rs index 8a3f7b0..1656f3c 100644 --- a/src/egs/manifest_item.rs +++ b/src/egs/manifest_item.rs @@ -100,7 +100,7 @@ impl ManifestItem { ) } - pub fn get_key(&self) -> String{ + pub fn get_key(&self) -> String { format!( "{}-{}-{}", self.catalog_namespace, self.catalog_item_id, self.app_name @@ -108,7 +108,7 @@ impl ManifestItem { } fn needs_launcher(&self) -> bool { - if self.safe_launch{ + if self.safe_launch { return true; } match (&self.is_managed, &self.expected_dlc) { @@ -157,7 +157,7 @@ mod tests { let mut manifest: ManifestItem = serde_json::from_str(json).unwrap(); manifest.is_managed = false; manifest.expected_dlc = None; - let shortcut: ShortcutOwned = manifest.clone().into(); + let shortcut: ShortcutOwned = manifest.into(); #[cfg(target_os = "windows")] assert_eq!(shortcut.exe, "C:\\Games\\MarvelGOTG\\retail/gotg.exe"); @@ -172,7 +172,7 @@ mod tests { let json = include_str!("example_item.json"); let mut manifest: ManifestItem = serde_json::from_str(json).unwrap(); manifest.is_managed = false; - let shortcut: ShortcutOwned = manifest.clone().into(); + let shortcut: ShortcutOwned = manifest.into(); let expected ="com.epicgames.launcher://apps/2a09fb19b47f46dfb11ebd382f132a8f%3A88f4bb0bb06e4962a2042d5e20fb6ace%3A63a665088eb1480298f1e57943b225d8?action=launch&silent=true"; let actual = shortcut.exe; diff --git a/src/egs/settings.rs b/src/egs/settings.rs index b097d44..9464bca 100644 --- a/src/egs/settings.rs +++ b/src/egs/settings.rs @@ -8,5 +8,5 @@ pub struct EpicGamesLauncherSettings { #[cfg(target_family = "unix")] pub create_symlinks: bool, - pub safe_launch : Vec + pub safe_launch: Vec, } diff --git a/src/gog/gog_game.rs b/src/gog/gog_game.rs index 49e3bc8..3c39f0e 100644 --- a/src/gog/gog_game.rs +++ b/src/gog/gog_game.rs @@ -24,7 +24,6 @@ pub(crate) struct PlayTask { #[serde(alias = "workingDir")] pub working_dir: Option, pub arguments: Option, - } #[derive(Clone)] @@ -48,13 +47,13 @@ impl From for ShortcutOwned { exe.to_str().unwrap_or("").to_string() }; let mut exe_string = exe.to_string_lossy().to_string(); - if exe_string.contains(" ") && !exe_string.starts_with("\""){ - exe_string = format!("\"{}\"",exe_string); + if exe_string.contains(' ') && !exe_string.starts_with('\"') { + exe_string = format!("\"{}\"", exe_string); } let mut working_dir_string = gogs.working_dir; - if working_dir_string.contains(" ") && !working_dir_string.starts_with("\""){ - working_dir_string = format!("\"{}\"",working_dir_string); + if working_dir_string.contains(' ') && !working_dir_string.starts_with('\"') { + working_dir_string = format!("\"{}\"", working_dir_string); } let shortcut = Shortcut::new( diff --git a/src/gog/gog_platform.rs b/src/gog/gog_platform.rs index af31c49..389e030 100644 --- a/src/gog/gog_platform.rs +++ b/src/gog/gog_platform.rs @@ -12,12 +12,13 @@ pub struct GogPlatform { pub settings: GogSettings, } - - -pub fn get_shortcuts_from_config(wine_c_drive : Option, config_path: PathBuf) -> Result, GogErrors> { +pub fn get_shortcuts_from_config( + _wine_c_drive: Option, + config_path: PathBuf, +) -> Result, GogErrors> { let install_locations = get_install_locations(config_path)?; #[cfg(target_family = "unix")] - let install_locations = if let Some(wine_c_drive) = &wine_c_drive { + let install_locations = if let Some(wine_c_drive) = &_wine_c_drive { fix_paths(wine_c_drive, install_locations) } else { install_locations @@ -44,14 +45,13 @@ pub fn get_shortcuts_from_config(wine_c_drive : Option, config_path: Pat pub fn get_shortcuts_from_game_folders(game_folders: Vec) -> Vec { let games = get_games_from_game_folders(game_folders); - let shortcuts = get_shortcuts_from_games(games); - shortcuts + + get_shortcuts_from_games(games) } fn get_shortcuts_from_games(games: Vec<(GogGame, PathBuf)>) -> Vec { let mut shortcuts = vec![]; for (game, game_folder) in games { - if let Some(folder_path) = game_folder.to_str() { if let Some(tasks) = &game.play_tasks { if let Some(primary_task) = tasks.iter().find(|t| { @@ -68,19 +68,23 @@ fn get_shortcuts_from_games(games: Vec<(GogGame, PathBuf)>) -> Vec Some(working_dir) => game_folder .join(working_dir) .to_str() - .unwrap_or_else(|| folder_path.as_str()) + .unwrap_or(folder_path.as_str()) .to_string(), None => folder_path.to_string(), }; #[cfg(target_family = "unix")] - let working_dir = working_dir.replace("\\", "/"); + let working_dir = working_dir.replace('\\', "/"); let full_path_string = full_path.to_string(); #[cfg(target_family = "unix")] - let full_path_string = full_path_string.replace("\\", "/"); - let arguments = primary_task.arguments.as_ref().unwrap_or(&"".to_string()).clone(); + let full_path_string = full_path_string.replace('\\', "/"); + let arguments = primary_task + .arguments + .as_ref() + .unwrap_or(&"".to_string()) + .clone(); let shortcut = GogShortcut { name: game.name, game_folder: folder_path, @@ -133,7 +137,6 @@ fn get_games_from_game_folders(game_folders: Vec) -> Vec<(GogGame, Path games } - impl Platform for GogPlatform { fn enabled(&self) -> bool { self.settings.enabled @@ -162,7 +165,7 @@ impl Platform for GogPlatform { if !config_path.exists() { return Err(GogErrors::ConfigFileNotFound { path: config_path }); } - get_shortcuts_from_config(self.settings.wine_c_drive.clone(),config_path) + get_shortcuts_from_config(self.settings.wine_c_drive.clone(), config_path) } fn settings_valid(&self) -> crate::platform::SettingsValidity { @@ -177,10 +180,10 @@ impl Platform for GogPlatform { } fn needs_proton(&self, _input: &GogShortcut) -> bool { - #[cfg(target_family = "unix")] - return true; - #[cfg(target_os = "windows")] - return false; + #[cfg(target_family = "unix")] + return true; + #[cfg(target_os = "windows")] + return false; } } @@ -191,7 +194,7 @@ fn fix_paths(wine_c_drive: &str, paths: Vec) -> Vec { .flat_map(|path| { if let Some(stripped) = path.strip_prefix("C:\\") { let path_buf = Path::new(wine_c_drive).join(stripped); - path_buf.to_str().map(|s| s.to_string().replace("\\", "/")) + path_buf.to_str().map(|s| s.to_string().replace('\\', "/")) } else { None } diff --git a/src/gog/mod.rs b/src/gog/mod.rs index f7b2980..b4acfb5 100644 --- a/src/gog/mod.rs +++ b/src/gog/mod.rs @@ -3,6 +3,6 @@ mod gog_game; mod gog_platform; mod gog_settings; +pub use gog_game::GogShortcut; pub use gog_platform::*; pub use gog_settings::*; -pub use gog_game::GogShortcut; diff --git a/src/heroic/heroic_game.rs b/src/heroic/heroic_game.rs index 3440e91..ca4d1f8 100644 --- a/src/heroic/heroic_game.rs +++ b/src/heroic/heroic_game.rs @@ -13,23 +13,23 @@ pub struct HeroicGame { pub launch_parameters: String, } - - -impl HeroicGame{ - pub fn is_installed(&self) -> bool{ - Path::new(&self.install_path).join(&self.executable).exists() +impl HeroicGame { + pub fn is_installed(&self) -> bool { + Path::new(&self.install_path) + .join(&self.executable) + .exists() } } impl From for ShortcutOwned { fn from(game: HeroicGame) -> Self { let target_path = Path::new(&game.install_path).join(game.executable); - + #[cfg(target_family = "unix")] let mut target = target_path.to_string_lossy().to_string(); #[cfg(target_family = "unix")] { - if !target.starts_with("\"") && !target.ends_with("\"") { + if !target.starts_with('\"') && !target.ends_with('\"') { target = format!("\"{}\"", target); } } @@ -38,12 +38,12 @@ impl From for ShortcutOwned { let mut install_path = game.install_path.to_string(); #[cfg(target_family = "unix")] { - if !install_path.starts_with("\"") && !install_path.ends_with("\"") { + if !install_path.starts_with('\"') && !install_path.ends_with('\"') { install_path = format!("\"{}\"", install_path); } } #[cfg(target_os = "windows")] - let install_path = game.install_path.to_string(); + let install_path = game.install_path.to_string(); #[cfg(target_os = "windows")] let target = target_path.to_string_lossy().to_string(); diff --git a/src/heroic/heroic_game_type.rs b/src/heroic/heroic_game_type.rs index d7668e5..13dd7c9 100644 --- a/src/heroic/heroic_game_type.rs +++ b/src/heroic/heroic_game_type.rs @@ -1,22 +1,20 @@ use steam_shortcuts_util::shortcut::ShortcutOwned; -use crate::gog::GogShortcut; use super::HeroicGame; - +use crate::gog::GogShortcut; #[derive(Clone)] -pub enum HeroicGameType{ +pub enum HeroicGameType { Epic(HeroicGame), //The bool is if it is windows (true) or not (false) - Gog(GogShortcut,bool) + Gog(GogShortcut, bool), } - impl From for ShortcutOwned { fn from(heroic_game_type: HeroicGameType) -> Self { - match heroic_game_type{ + match heroic_game_type { HeroicGameType::Epic(epic) => epic.into(), - HeroicGameType::Gog(gog,_) => gog.into(), + HeroicGameType::Gog(gog, _) => gog.into(), } } } diff --git a/src/heroic/heroic_platform.rs b/src/heroic/heroic_platform.rs index aba219c..49aa16c 100644 --- a/src/heroic/heroic_platform.rs +++ b/src/heroic/heroic_platform.rs @@ -1,7 +1,7 @@ -use serde::{Deserialize}; +use serde::Deserialize; use super::{HeroicGame, HeroicGameType, HeroicSettings}; -use crate::gog::{ get_shortcuts_from_game_folders}; +use crate::gog::get_shortcuts_from_game_folders; use crate::platform::{Platform, SettingsValidity}; use std::collections::HashMap; use std::error::Error; @@ -48,7 +48,7 @@ fn get_gog_installed_location(install_mode: &InstallationMode) -> PathBuf { InstallationMode::UserBin => { Path::new(&home_dir).join(".config/heroic/gog_store/installed.json") } - } + } } fn get_shortcuts_from_install_mode( @@ -68,7 +68,7 @@ fn get_shortcuts_from_location>(path: P) -> Result> for HeroicPlatform { } fn needs_proton(&self, input: &HeroicGameType) -> bool { - match input{ + match input { HeroicGameType::Epic(_) => true, HeroicGameType::Gog(_, is_windows) => *is_windows, } @@ -150,7 +150,7 @@ fn get_gog_games( }) .filter_map(|config_path| std::fs::read_to_string(config_path).ok()) .filter_map(|config_string| serde_json::from_str::(&config_string).ok()) - .flat_map(|config| config.installed) + .flat_map(|config| config.installed) .collect(); let mut is_windows_map = HashMap::new(); @@ -170,9 +170,9 @@ fn get_gog_games( } }) .collect(); - let shortcuts = get_shortcuts_from_game_folders(game_folders); + let shortcuts = get_shortcuts_from_game_folders(game_folders); let mut gog_shortcuts = vec![]; - for shortcut in shortcuts { + for shortcut in shortcuts { let is_windows = is_windows_map.get(&shortcut.game_id).unwrap_or(&false); gog_shortcuts.push(HeroicGameType::Gog(shortcut, *is_windows)); } diff --git a/src/heroic/mod.rs b/src/heroic/mod.rs index 7b94b10..c0a9fd7 100644 --- a/src/heroic/mod.rs +++ b/src/heroic/mod.rs @@ -1,9 +1,9 @@ mod heroic_game; +mod heroic_game_type; mod heroic_platform; mod settings; -mod heroic_game_type; pub use heroic_game::*; +pub use heroic_game_type::*; pub use heroic_platform::*; pub use settings::*; -pub use heroic_game_type::*; diff --git a/src/itch/butler_db_parser.rs b/src/itch/butler_db_parser.rs index 53d0617..03ea668 100644 --- a/src/itch/butler_db_parser.rs +++ b/src/itch/butler_db_parser.rs @@ -4,8 +4,7 @@ use nom::{ IResult, }; -use serde::{Deserialize}; - +use serde::Deserialize; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub(crate) struct DbPaths { @@ -14,31 +13,31 @@ pub(crate) struct DbPaths { } #[derive(Deserialize, Debug, Clone)] -struct Candidate{ - pub path:String, +struct Candidate { + pub path: String, } -pub(crate) fn parse_butler_db<'a>(content: &'a [u8]) -> nom::IResult<&[u8], Vec> { +pub(crate) fn parse_butler_db(content: &[u8]) -> nom::IResult<&[u8], Vec> { many0(parse_path)(content) } -fn parse_path<'a>(i: &'a [u8]) -> nom::IResult<&[u8], DbPaths> { +fn parse_path(i: &[u8]) -> nom::IResult<&[u8], DbPaths> { let prefix = "{\"basePath\":\""; let suffix = "\",\"totalSize\""; let (i, _taken) = take_until(prefix)(i)?; let (i, _taken) = tag(prefix)(i)?; let (i, base_path) = take_until(suffix)(i)?; let base_path = String::from_utf8_lossy(base_path).to_string(); - + let prefix = "\"candidates\":["; let suffix = "]}"; let (i, _taken) = take_until(prefix)(i)?; let (i, _taken) = tag(prefix)(i)?; let (i, candidates_json) = take_until(suffix)(i)?; - let candidates_json = format!("[{}]",String::from_utf8_lossy(candidates_json).to_string()); + let candidates_json = format!("[{}]", String::from_utf8_lossy(candidates_json)); let candidates = serde_json::from_str::>(&candidates_json); - match candidates{ + match candidates { Ok(candidates) => { return IResult::Ok(( i, @@ -47,17 +46,17 @@ fn parse_path<'a>(i: &'a [u8]) -> nom::IResult<&[u8], DbPaths> { paths: candidates.iter().map(|c| c.path.clone()).collect(), }, )) - }, + } Err(_err) => { //we found a basepath, but no executables - return IResult::Ok(( + IResult::Ok(( i, DbPaths { base_path, paths: vec![], }, )) - }, + } } } @@ -105,12 +104,13 @@ mod tests { let (_r, paths) = result.unwrap(); assert_eq!(paths.len(), 94); - assert_eq!(paths[0].base_path, "/home/deck/.config/itch/apps/risetoruins"); + assert_eq!( + paths[0].base_path, + "/home/deck/.config/itch/apps/risetoruins" + ); assert_eq!(paths[0].paths[0], "Core.jar"); - //The parser finds douplicates + //The parser finds douplicates assert_eq!(paths[0], paths[1]); assert_eq!(paths[1], paths[2]); - - } } diff --git a/src/itch/itch_platform.rs b/src/itch/itch_platform.rs index 8593aa7..4cdf5d7 100644 --- a/src/itch/itch_platform.rs +++ b/src/itch/itch_platform.rs @@ -71,13 +71,13 @@ impl Platform for ItchPlatform { } #[cfg(target_os = "windows")] fn needs_proton(&self, _input: &ItchGame) -> bool { - return false; + false } - + #[cfg(target_family = "unix")] - fn needs_proton(&self, input: &ItchGame) -> bool { + fn needs_proton(&self, input: &ItchGame) -> bool { //We can only really guess here - return input.executable.ends_with("exe"); + input.executable.ends_with("exe") } } diff --git a/src/legendary/legendary_platform.rs b/src/legendary/legendary_platform.rs index 139591a..eb8cd7f 100644 --- a/src/legendary/legendary_platform.rs +++ b/src/legendary/legendary_platform.rs @@ -28,7 +28,7 @@ impl Platform> for LegendaryPlatform { .settings .executable .clone() - .unwrap_or("legendary".to_string()); + .unwrap_or_else(|| "legendary".to_string()); let legendary = legendary_string.as_str(); execute_legendary_command(legendary) } @@ -49,7 +49,7 @@ impl Platform> for LegendaryPlatform { } fn needs_proton(&self, _input: &LegendaryGame) -> bool { - return false; + false } } diff --git a/src/lutris/game_list_parser.rs b/src/lutris/game_list_parser.rs index d063abb..f49a7fd 100644 --- a/src/lutris/game_list_parser.rs +++ b/src/lutris/game_list_parser.rs @@ -1,16 +1,16 @@ use super::lutris_game::LutrisGame; -pub fn parse_lutris_games<'a>(input: &'a str) -> Vec { +pub fn parse_lutris_games(input: &str) -> Vec { input - .split("\n") + .split('\n') .into_iter() .filter(|s| !s.is_empty()) .filter_map(parse_line) .collect() } -fn parse_line<'a>(input: &'a str) -> Option { - let mut sections = input.split("|"); +fn parse_line(input: &str) -> Option { + let mut sections = input.split('|'); if sections.clone().count() < 4 { return None; } @@ -20,10 +20,10 @@ fn parse_line<'a>(input: &'a str) -> Option { let platform = sections.next().unwrap().trim(); Some(LutrisGame { - id:id.to_string(), - index:index.to_string(), - name:name.to_string(), - platform:platform.to_string(), + id: id.to_string(), + index: index.to_string(), + name: name.to_string(), + platform: platform.to_string(), }) } diff --git a/src/lutris/lutris_game.rs b/src/lutris/lutris_game.rs index 64080ff..b7f2ccc 100644 --- a/src/lutris/lutris_game.rs +++ b/src/lutris/lutris_game.rs @@ -18,7 +18,7 @@ impl From for ShortcutOwned { "", "", "", - &options.as_str(), + options.as_str(), ) .to_owned() } diff --git a/src/lutris/lutris_platform.rs b/src/lutris/lutris_platform.rs index 6925d6d..ef4d50a 100644 --- a/src/lutris/lutris_platform.rs +++ b/src/lutris/lutris_platform.rs @@ -53,6 +53,6 @@ impl Platform> for LutrisPlatform { } fn needs_proton(&self, _input: &LutrisGame) -> bool { - return false; + false } } diff --git a/src/lutris/settings.rs b/src/lutris/settings.rs index 810a6f6..d8f8dc2 100644 --- a/src/lutris/settings.rs +++ b/src/lutris/settings.rs @@ -1,7 +1,7 @@ -use serde::{Serialize,Deserialize}; +use serde::{Deserialize, Serialize}; -#[derive(Debug, Deserialize, Serialize,Clone)] +#[derive(Debug, Deserialize, Serialize, Clone)] pub struct LutrisSettings { pub enabled: bool, pub executable: Option, -} \ No newline at end of file +} diff --git a/src/main.rs b/src/main.rs index 3a3cbf7..c72488d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +mod amazon; mod egs; mod gog; mod heroic; @@ -10,9 +11,8 @@ mod settings; mod steam; mod steamgriddb; mod sync; -mod uplay; mod ui; -mod amazon; +mod uplay; use std::error::Error; fn main() -> Result<(), Box> { @@ -20,7 +20,7 @@ fn main() -> Result<(), Box> { if args.len() > 1 && args.nth(1).unwrap_or_default() == "--no-ui" { ui::run_sync(); Ok(()) - }else{ + } else { ui::run_ui() } -} \ No newline at end of file +} diff --git a/src/origin/origin_game.rs b/src/origin/origin_game.rs index 396d364..d62e3ef 100644 --- a/src/origin/origin_game.rs +++ b/src/origin/origin_game.rs @@ -6,34 +6,30 @@ use steam_shortcuts_util::{shortcut::ShortcutOwned, Shortcut}; pub struct OriginGame { pub id: String, pub title: String, - pub origin_location : Option, + pub origin_location: Option, } impl From for ShortcutOwned { fn from(game: OriginGame) -> Self { - let launch = format!("\"origin2://game/launch?offerIds={}&autoDownload=1&authCode=&cmdParams=\"", game.id); - - let mut owned_shortcut = if let Some(origin_location) = game.origin_location{ - let origin_location = format!("\"{}\"",origin_location.to_string_lossy()); - Shortcut::new( - "0", - game.title.as_str(), - &origin_location, - "", - "", - "", - launch.as_str() - ).to_owned() - }else{ + let launch = format!( + "\"origin2://game/launch?offerIds={}&autoDownload=1&authCode=&cmdParams=\"", + game.id + ); + + let mut owned_shortcut = if let Some(origin_location) = game.origin_location { + let origin_location = format!("\"{}\"", origin_location.to_string_lossy()); Shortcut::new( - "0", - game.title.as_str(), - launch.as_str(), - "", - "", - "", - "" - ).to_owned() + "0", + game.title.as_str(), + &origin_location, + "", + "", + "", + launch.as_str(), + ) + .to_owned() + } else { + Shortcut::new("0", game.title.as_str(), launch.as_str(), "", "", "", "").to_owned() }; owned_shortcut.tags.push("Origin".to_owned()); owned_shortcut.tags.push("Ready TO Play".to_owned()); diff --git a/src/origin/origin_platform.rs b/src/origin/origin_platform.rs index 438e63d..b1c2e1e 100644 --- a/src/origin/origin_platform.rs +++ b/src/origin/origin_platform.rs @@ -27,22 +27,21 @@ impl Platform for OriginPlatform { } fn get_shortcuts(&self) -> Result, OriginErrors> { - - let origin_folders = get_default_locations(); - if origin_folders.local_content_path.is_none(){ - return Err(OriginErrors::PathNotFound { path: "Default path".to_string() }); + let origin_folders = get_default_locations(); + if origin_folders.local_content_path.is_none() { + return Err(OriginErrors::PathNotFound { + path: "Default path".to_string(), + }); } - + let origin_folder = origin_folders.local_content_path.unwrap(); let origin_exe = origin_folders.exe_path; - let game_folders = - origin_folder - .join("LocalContent") - .read_dir() - .map_err(|e| OriginErrors::CouldNotReadGameDir { - path: origin_folder.join("LocalContent"), - error: format!("{:?}", e), - })?; + let game_folders = origin_folder.join("LocalContent").read_dir().map_err(|e| { + OriginErrors::CouldNotReadGameDir { + path: origin_folder.join("LocalContent"), + error: format!("{:?}", e), + } + })?; let games = game_folders .filter_map(|folder| folder.ok()) .filter_map(|game_folder| { @@ -57,7 +56,7 @@ impl Platform for OriginPlatform { id.map(|id| OriginGame { id, title: game_title, - origin_location:origin_exe.clone() + origin_location: origin_exe.clone(), }) }); Ok(games.collect()) @@ -79,12 +78,11 @@ impl Platform for OriginPlatform { #[cfg(target_family = "unix")] { //TODO Update this when origin gets support on linux - return true; + true } } } - fn get_folder_mfst_file_content(game_folder_path: &Path) -> Option { let game_folder_files = game_folder_path.read_dir(); if let Ok(game_folder_files) = game_folder_files { @@ -114,47 +112,44 @@ fn parse_id_from_file(i: &str) -> nom::IResult<&str, &str> { take_until("&")(i) } - #[derive(Default)] -struct OriginPathData{ +struct OriginPathData { //~/.steam/steam/steamapps/compatdata/X/pfx/drive_c/Program Files (x86)/Origin/Origin.exe - exe_path:Option, + exe_path: Option, //~/.steam/steam/steamapps/compatdata/X/pfx/drive_c/ProgramData/Origin/LocalContent - local_content_path:Option + local_content_path: Option, } - - #[cfg(target_family = "unix")] - fn get_default_locations() -> OriginPathData { +fn get_default_locations() -> OriginPathData { let mut res = OriginPathData::default(); - if let Ok(home) = std::env::var("HOME"){ + if let Ok(home) = std::env::var("HOME") { let compat_folder_path = Path::new(&home) - .join(".steam") - .join("steam") - .join("steamapps") - .join("compatdata"); - - if let Ok(compat_folder) = std::fs::read_dir(&compat_folder_path){ - for game_folder in compat_folder { - if let Ok(dir) = game_folder{ - let origin_exe_path= dir.path() + .join(".steam") + .join("steam") + .join("steamapps") + .join("compatdata"); + + if let Ok(compat_folder) = std::fs::read_dir(&compat_folder_path) { + for dir in compat_folder.flatten() { + let origin_exe_path = dir + .path() .join("pfx") .join("drive_c") .join("Program Files (x86)") .join("Origin") .join("Origin.exe"); - let origin_local_content= dir.path() + let origin_local_content = dir + .path() .join("pfx") .join("drive_c") .join("ProgramData") .join("Origin"); - - if origin_exe_path.exists() && origin_local_content.exists(){ - res.exe_path = Some(origin_exe_path); - res.local_content_path = Some(origin_local_content); - } + + if origin_exe_path.exists() && origin_local_content.exists() { + res.exe_path = Some(origin_exe_path); + res.local_content_path = Some(origin_local_content); } } } @@ -162,20 +157,16 @@ struct OriginPathData{ res } - - - #[cfg(target_os = "windows")] fn get_default_locations() -> OriginPathData { let mut res = OriginPathData::default(); let key = "PROGRAMDATA"; let program_data = std::env::var(key); - if let Ok(program_data) =program_data { - let origin_folder = Path::new(&program_data) - .join("Origin"); - if origin_folder.exists(){ - res.local_content_path = Some(origin_folder.to_owned()); + if let Ok(program_data) = program_data { + let origin_folder = Path::new(&program_data).join("Origin"); + if origin_folder.exists() { + res.local_content_path = Some(origin_folder); } } res diff --git a/src/platform.rs b/src/platform.rs index 6700541..623657b 100644 --- a/src/platform.rs +++ b/src/platform.rs @@ -1,4 +1,4 @@ - use steam_shortcuts_util::shortcut::ShortcutOwned; +use steam_shortcuts_util::shortcut::ShortcutOwned; pub trait Platform where diff --git a/src/settings.rs b/src/settings.rs index 19c61d8..4c8bc76 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,7 +1,8 @@ use crate::{ - egs::EpicGamesLauncherSettings, gog::GogSettings, itch::ItchSettings, - legendary::LegendarySettings, lutris::settings::LutrisSettings, origin::OriginSettings, - steam::SteamSettings, steamgriddb::SteamGridDbSettings, uplay::UplaySettings, heroic::HeroicSettings, amazon::AmazonSettings, + amazon::AmazonSettings, egs::EpicGamesLauncherSettings, gog::GogSettings, + heroic::HeroicSettings, itch::ItchSettings, legendary::LegendarySettings, + lutris::settings::LutrisSettings, origin::OriginSettings, steam::SteamSettings, + steamgriddb::SteamGridDbSettings, uplay::UplaySettings, }; use config::{Config, ConfigError, Environment, File}; diff --git a/src/steam/collections.rs b/src/steam/collections.rs index 623417d..e5eed45 100644 --- a/src/steam/collections.rs +++ b/src/steam/collections.rs @@ -42,7 +42,7 @@ struct ActualSteamCollection { } impl ActualSteamCollection { - fn new>(name: B, ids: &Vec) -> Self { + fn new>(name: B, ids: &[usize]) -> Self { let name = name.as_ref(); let key = format!("user-collections.{}", name_to_key(name)); let value = serialize_collection_value(name, ids); @@ -80,13 +80,13 @@ impl ValueCollection { fn new>(name: S, game_ids: &[usize]) -> Self { let name = name.as_ref(); let id = name_to_key(name); - let value = ValueCollection { + + ValueCollection { id, name: name.to_string(), added: game_ids.to_vec(), removed: vec![], - }; - value + } } } @@ -106,7 +106,7 @@ pub struct Collection { pub fn write_collections>( steam_user_id: S, - collections_to_add: &Vec, + collections_to_add: &[Collection], ) -> Result<(), Box> { let steam_user_id = steam_user_id.as_ref(); let new_collections: Vec<(String, SteamCollection)> = collections_to_add @@ -134,7 +134,7 @@ pub fn write_collections>( let boilr_keys: Vec = vdf_collections .keys() .filter(|k| k.contains(BOILR_TAG)) - .map(|k| k.clone()) + .cloned() .collect(); for key in boilr_keys { vdf_collections.remove(&key); @@ -142,12 +142,12 @@ pub fn write_collections>( let new_vdfs = collections_to_add.iter().map(|collection| { let key = name_to_key(&collection.name); - let vd_collection = VdfCollection { + + VdfCollection { id: key, added: collection.game_ids.clone(), removed: vec![], - }; - vd_collection + } }); for new_vdf in new_vdfs { vdf_collections.insert(new_vdf.id.clone(), new_vdf.clone()); @@ -181,11 +181,11 @@ fn get_vdf_path>(steamid: S) -> Option { .join("config") .join("localconfig.vdf"); if path.exists() { - return Some(path.to_path_buf()); + return Some(path); } - return None; + None } - Err(_e) => return None, + Err(_e) => None, } } @@ -200,7 +200,7 @@ fn get_vdf_path>(steamid: S) -> Option { .join("config") .join("localconfig.vdf"); if path.exists() { - Some(path.to_path_buf()) + Some(path) } else { None } @@ -220,10 +220,12 @@ fn save_category>( Ok(()) } +type CollectionCategories = HashMap>; + fn get_categories>( steamid: S, db: &mut DB, -) -> Result>, Box> { +) -> Result> { let namespace_keys = get_namespace_keys(steamid, db); let mut db_iter = db.new_iter()?; let mut res = HashMap::new(); @@ -295,11 +297,11 @@ fn get_level_db_location() -> Option { .join("Local Storage") .join("leveldb"); if path.exists() { - return Some(path.to_path_buf()); + return Some(path); } - return None; + None } - Err(_e) => return None, + Err(_e) => None, } } @@ -314,7 +316,7 @@ fn get_level_db_location() -> Option { .join("leveldb"); if path.exists() { Some(path) - }else{ + } else { None } } @@ -324,7 +326,7 @@ fn get_level_db_location() -> Option { fn serialize_collection_value>(name: S, game_ids: &[usize]) -> String { let value = ValueCollection::new(name, game_ids); - serde_json::to_string(&value).expect("Should be able to serialize known type") + serde_json::to_string(&value).expect("Should be able to serialize known type") } fn name_to_key>(name: S) -> String { @@ -339,7 +341,7 @@ fn parse_steam_collections>( ) -> Result, Box> { let input = input.as_ref(); let input = input.strip_prefix('\u{1}').unwrap_or(input); - let res = serde_json::from_str::>(&input)?; + let res = serde_json::from_str::>(input)?; Ok(res) } diff --git a/src/steam/mod.rs b/src/steam/mod.rs index e0948c9..4623fb2 100644 --- a/src/steam/mod.rs +++ b/src/steam/mod.rs @@ -1,12 +1,11 @@ -mod settings; -mod utils; mod collections; mod proton_vdf_util; mod restarter; +mod settings; +mod utils; - -pub use settings::SteamSettings; -pub use utils::*; pub use collections::*; pub use proton_vdf_util::*; -pub use restarter::*; \ No newline at end of file +pub use restarter::*; +pub use settings::SteamSettings; +pub use utils::*; diff --git a/src/steam/proton_vdf_util.rs b/src/steam/proton_vdf_util.rs index 5a7f354..17353b1 100644 --- a/src/steam/proton_vdf_util.rs +++ b/src/steam/proton_vdf_util.rs @@ -2,19 +2,16 @@ use std::path::Path; use nom::FindSubstring; - -pub fn setup_proton_games>(games: &[B]){ - if let Ok(home) = std::env::var("HOME"){ +pub fn setup_proton_games>(games: &[B]) { + if let Ok(home) = std::env::var("HOME") { let config_file = Path::new(&home).join(".local/share/Steam/config/config.vdf"); - if config_file.exists(){ - if let Ok(config_content) = std::fs::read_to_string(&config_file){ + if config_file.exists() { + if let Ok(config_content) = std::fs::read_to_string(&config_file) { let new_string = enable_proton_games(config_content, games); std::fs::write(config_file, new_string).unwrap(); } } } - - } fn enable_proton_games, B: AsRef>(vdf_content: S, games: &[B]) -> String { @@ -44,7 +41,6 @@ fn enable_proton_games, B: AsRef>(vdf_content: S, games: &[B] let res = res.replace("\"X\"", &format!("\"{}\"", game_id.as_ref())); let res = res.replace('=', &base_indent_string); res.replace('+', &field_indent_string) - }); let mut new_section = section_str.to_string(); for game_string in games_strings_to_add { @@ -116,22 +112,21 @@ mod tests { let expected = include_str!("../testdata/vdf/compatmappingsection.vdf"); assert_eq!(expected, actual); assert_eq!(4, base_indentation); - } #[test] pub fn enable_proton_test() { let input = include_str!("../testdata/vdf/testconfig.vdf"); - let output = enable_proton_games(input, &vec!["42", "43", "44"]); + let output = enable_proton_games(input, &["42", "43", "44"]); let expected = include_str!("../testdata/vdf/testconfig_expected.vdf"); - assert_eq!(expected,output ); + assert_eq!(expected, output); } #[test] pub fn enable_proton_test_empty() { let input = include_str!("../testdata/vdf/testconfig.vdf"); - let output = enable_proton_games(input, &vec!["2719403116"]); + let output = enable_proton_games(input, &["2719403116"]); let expected = include_str!("../testdata/vdf/testconfig.vdf"); - assert_eq!(expected,output ); + assert_eq!(expected, output); } } diff --git a/src/steam/restarter.rs b/src/steam/restarter.rs index 2989827..da3f137 100644 --- a/src/steam/restarter.rs +++ b/src/steam/restarter.rs @@ -1,4 +1,4 @@ -use std::{path::Path, process::Command, thread::sleep, time::Duration}; +use std::{process::Command, thread::sleep, time::Duration}; use sysinfo::{ProcessExt, System, SystemExt}; @@ -7,9 +7,9 @@ pub fn ensure_steam_stopped() { let steam_name = "steam.exe"; #[cfg(target_family = "unix")] let steam_name = "steam"; - + let s = System::new_all(); - let processes = s.processes_by_name(&steam_name); + let processes = s.processes_by_name(steam_name); for process in processes { let mut s = System::new(); process.kill_with(sysinfo::Signal::Quit); @@ -26,13 +26,13 @@ pub fn ensure_steam_stopped() { pub fn ensure_steam_started(settings: &super::SteamSettings) { let steam_name = "steam.exe"; let s = System::new_all(); - let mut processes = s.processes_by_name(&steam_name); + let mut processes = s.processes_by_name(steam_name); if processes.next().is_none() { //no steam, we need to start it println!("Starting steam"); let folder = super::get_steam_path(settings); if let Ok(folder) = folder { - let path = Path::new(&folder).join(steam_name); + let path = std::path::Path::new(&folder).join(steam_name); let mut command = Command::new(&path); if let Err(e) = command.spawn() { println!("Failed to start steam: {:?}", e); @@ -45,7 +45,7 @@ pub fn ensure_steam_started(settings: &super::SteamSettings) { pub fn ensure_steam_started(_settings: &super::SteamSettings) { let steam_name = "steam"; let s = System::new_all(); - let mut processes = s.processes_by_name(&steam_name); + let mut processes = s.processes_by_name(steam_name); if processes.next().is_none() { //no steam, we need to start it println!("Starting steam"); @@ -54,4 +54,4 @@ pub fn ensure_steam_started(_settings: &super::SteamSettings) { println!("Failed to start steam: {:?}", e); }; } -} \ No newline at end of file +} diff --git a/src/steam/utils.rs b/src/steam/utils.rs index 7095be2..e81c804 100644 --- a/src/steam/utils.rs +++ b/src/steam/utils.rs @@ -43,10 +43,11 @@ pub struct ShortcutInfo { pub shortcuts: Vec, } +#[derive(Default, PartialEq, Clone)] pub struct SteamUsersInfo { pub steam_user_data_folder: String, pub shortcut_path: Option, - pub user_id : String, + pub user_id: String, } /// Get the paths to the steam users shortcuts (one for each user) @@ -89,14 +90,14 @@ pub fn get_shortcuts_paths( return SteamUsersInfo { steam_user_data_folder: folder_string, shortcut_path: Some(shortcuts_path.to_str().unwrap().to_string()), - user_id + user_id, }; } else { - return SteamUsersInfo { + SteamUsersInfo { steam_user_data_folder: folder_string, shortcut_path: None, - user_id - }; + user_id, + } } }) .collect(); @@ -203,6 +204,3 @@ pub fn get_users_images(user: &SteamUsersInfo) -> Result, Box CachedSearch<'a> { save_search_map(&self.search_map); } + pub fn set_cache(&mut self, app_id: u32, name:S, new_grid_id:usize) + where + S: Into{ + self.search_map.insert(app_id,(name.into(),new_grid_id)); + self.save(); + } + pub async fn search( &self, app_id: u32, diff --git a/src/steamgriddb/downloader.rs b/src/steamgriddb/downloader.rs index dbf9329..5697113 100644 --- a/src/steamgriddb/downloader.rs +++ b/src/steamgriddb/downloader.rs @@ -5,9 +5,9 @@ use std::{collections::HashMap, path::Path}; use futures::{stream, StreamExt}; use serde::{Deserialize, Serialize}; -use tokio::sync::watch::Sender; use std::error::Error; -use steamgriddb_api::query_parameters::{GridDimentions, Nsfw}; // 0.3.1 +use steamgriddb_api::query_parameters::{GridDimentions, Nsfw}; +use tokio::sync::watch::Sender; // 0.3.1 use steam_shortcuts_util::shortcut::ShortcutOwned; use steamgriddb_api::Client; @@ -25,7 +25,7 @@ pub async fn download_images_for_users<'b>( settings: &Settings, users: &[SteamUsersInfo], download_animated: bool, - sender:&mut Option> + sender: &mut Option>, ) { let auth_key = &settings.steamgrid_db.auth_key; if let Some(auth_key) = auth_key { @@ -35,7 +35,7 @@ pub async fn download_images_for_users<'b>( let search = CachedSearch::new(&client); let search = &search; let client = &client; - if let Some(sender) = sender{ + if let Some(sender) = sender { let _ = sender.send(SyncProgress::FindingImages); } let to_downloads = stream::iter(users) @@ -62,7 +62,7 @@ pub async fn download_images_for_users<'b>( let to_downloads = to_downloads.iter().flatten().collect::>(); let total = to_downloads.len(); if !to_downloads.is_empty() { - if let Some(sender) = sender{ + if let Some(sender) = sender { let _ = sender.send(SyncProgress::DownloadingImages { to_download: total }); } search.save(); @@ -82,7 +82,7 @@ pub async fn download_images_for_users<'b>( } } else { println!("Steamgrid DB Auth Key not found, please add one as described here: https://github.com/PhilipK/steam_shortcuts_sync#configuration"); - } + } } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -183,7 +183,7 @@ async fn search_for_images_to_download( let shortcuts: Vec<&ShortcutOwned> = images_needed.collect(); if let ImageType::Icon = image_type { - for (index,image_id) in image_ids.iter().enumerate() { + for (index, image_id) in image_ids.iter().enumerate() { let shortcut = shortcuts[index]; if let Some(url) = get_steam_icon_url(*image_id).await { let path = grid_folder.join(image_type.file_name(shortcut.app_id)); @@ -196,9 +196,9 @@ async fn search_for_images_to_download( } } } else { - for image_ids in image_ids.chunks(99){ + for image_ids in image_ids.chunks(99) { let image_search_result = - get_images_for_ids(client, &image_ids, &image_type, download_animated).await; + get_images_for_ids(client, image_ids, &image_type, download_animated).await; match image_search_result { Ok(images) => { let images = images @@ -239,21 +239,26 @@ async fn get_images_for_ids( image_ids: &[usize], image_type: &ImageType, download_animated: bool, -) -> Result< - Vec>, - String, -> { - use steamgriddb_api::query_parameters::AnimtionType; - use steamgriddb_api::query_parameters::QueryType::*; +) -> Result>, String> +{ + let query_type = get_query_type(download_animated, image_type); + + let image_search_result = client.get_images_for_ids(image_ids, &query_type).await; + + image_search_result.map_err(|e| format!("Image search failed {:?}", e)) +} + +const BIG_PICTURE_DIMS : [GridDimentions;2] = [GridDimentions::D920x430, GridDimentions::D460x215]; + +pub fn get_query_type(download_animated: bool, image_type: &ImageType) -> steamgriddb_api::QueryType { let anymation_type = if download_animated { - Some(&[AnimtionType::Animated][..]) + Some(&[steamgriddb_api::query_parameters::AnimtionType::Animated][..]) } else { None }; - let big_picture_dims = [GridDimentions::D920x430, GridDimentions::D460x215]; use steamgriddb_api::query_parameters::GridQueryParameters; let big_picture_parameters = GridQueryParameters { - dimentions: Some(&big_picture_dims), + dimentions: Some(&BIG_PICTURE_DIMS), types: anymation_type, nsfw: Some(&Nsfw::False), ..Default::default() @@ -275,19 +280,15 @@ async fn get_images_for_ids( nsfw: Some(&Nsfw::False), ..Default::default() }; - let query_type = match image_type { - ImageType::Hero => Hero(Some(hero_parameters)), - ImageType::BigPicture => Grid(Some(big_picture_parameters)), - ImageType::Grid => Grid(Some(grid_parameters)), - ImageType::WideGrid => Grid(Some(big_picture_parameters)), - ImageType::Logo => Logo(Some(logo_parameters)), + ImageType::Hero => steamgriddb_api::QueryType::Hero(Some(hero_parameters)), + ImageType::BigPicture => steamgriddb_api::QueryType::Grid(Some(big_picture_parameters)), + ImageType::Grid => steamgriddb_api::QueryType::Grid(Some(grid_parameters)), + ImageType::WideGrid => steamgriddb_api::QueryType::Grid(Some(big_picture_parameters)), + ImageType::Logo => steamgriddb_api::QueryType::Logo(Some(logo_parameters)), _ => panic!("Unsupported image type"), }; - - let image_search_result = client.get_images_for_ids(image_ids, &query_type).await; - - image_search_result.map_err(|e| format!("Image search failed {:?}",e)) + query_type } async fn get_steam_image_url(game_id: usize, image_type: &ImageType) -> Option { @@ -345,7 +346,7 @@ fn icon_url(steam_app_id: &str, icon_id: &str) -> String { ) } -async fn download_to_download(to_download: &ToDownload) -> Result<(), Box> { +pub async fn download_to_download(to_download: &ToDownload) -> Result<(), Box> { println!( "Downloading {:?} for {} to {:?}", to_download.image_type, to_download.app_name, to_download.path @@ -360,8 +361,11 @@ async fn download_to_download(to_download: &ToDownload) -> Result<(), Box &'static [ImageType;6]{ + &ALL_TYPES + } + + pub fn name(&self) -> &str{ + match self { + ImageType::Hero => "Hero", + ImageType::Grid => "Grid", + ImageType::WideGrid => "Wide Grid", + ImageType::Logo => "Logo", + ImageType::BigPicture => "Big Picture", + ImageType::Icon => "Icon", + } + } + pub fn file_name(&self, app_id: u32) -> String { match self { ImageType::Hero => format!("{}_hero.png", app_id), @@ -20,18 +39,31 @@ impl ImageType { } } - pub fn steam_url>(&self,steam_app_id:S,mtime:u64) -> String{ + pub fn steam_url>(&self, steam_app_id: S, mtime: u64) -> String { let steam_app_id = steam_app_id.as_ref(); - match self{ - ImageType::Hero => format!("https://cdn.cloudflare.steamstatic.com/steam/apps/{}/library_hero.jpg?t={}",steam_app_id,mtime), - ImageType::Grid => format!("https://cdn.cloudflare.steamstatic.com/steam/apps/{}/library_600x900_2x.jpg?t={}",steam_app_id,mtime), - ImageType::WideGrid => format!("https://cdn.cloudflare.steamstatic.com/steam/apps/{}/header.jpg?t={}",steam_app_id,mtime), - ImageType::Logo => format!("https://cdn.cloudflare.steamstatic.com/steam/apps/{}/logo.png?t={}",steam_app_id,mtime), - ImageType::BigPicture => format!("https://cdn.cloudflare.steamstatic.com/steam/apps/{}/header.jpg?t={}",steam_app_id,mtime), + match self { + ImageType::Hero => format!( + "https://cdn.cloudflare.steamstatic.com/steam/apps/{}/library_hero.jpg?t={}", + steam_app_id, mtime + ), + ImageType::Grid => format!( + "https://cdn.cloudflare.steamstatic.com/steam/apps/{}/library_600x900_2x.jpg?t={}", + steam_app_id, mtime + ), + ImageType::WideGrid => format!( + "https://cdn.cloudflare.steamstatic.com/steam/apps/{}/header.jpg?t={}", + steam_app_id, mtime + ), + ImageType::Logo => format!( + "https://cdn.cloudflare.steamstatic.com/steam/apps/{}/logo.png?t={}", + steam_app_id, mtime + ), + ImageType::BigPicture => format!( + "https://cdn.cloudflare.steamstatic.com/steam/apps/{}/header.jpg?t={}", + steam_app_id, mtime + ), // This should not happen - _ => "".to_string() - } + _ => "".to_string(), + } } - - } diff --git a/src/steamgriddb/settings.rs b/src/steamgriddb/settings.rs index fb94d4f..4c9fb37 100644 --- a/src/steamgriddb/settings.rs +++ b/src/steamgriddb/settings.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Serialize, Deserialize,Clone)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct SteamGridDbSettings { pub enabled: bool, pub auth_key: Option, diff --git a/src/sync/mod.rs b/src/sync/mod.rs index 230e452..0fc8806 100644 --- a/src/sync/mod.rs +++ b/src/sync/mod.rs @@ -2,8 +2,8 @@ mod symlinks; mod synchronization; -pub use synchronization::run_sync; pub use synchronization::download_images; pub use synchronization::get_platform_shortcuts; +pub use synchronization::run_sync; -pub use synchronization::SyncProgress; \ No newline at end of file +pub use synchronization::SyncProgress; diff --git a/src/sync/symlinks.rs b/src/sync/symlinks.rs index de637d8..3ce3b61 100644 --- a/src/sync/symlinks.rs +++ b/src/sync/symlinks.rs @@ -43,7 +43,7 @@ pub fn create_sym_links(shortcut: &ShortcutOwned) -> ShortcutOwned { ); let mut new_shortcut = new_shortcut.to_owned(); new_shortcut.tags = shortcut.tags.clone(); - new_shortcut.dev_kit_game_id = shortcut.dev_kit_game_id.clone(); + new_shortcut.dev_kit_game_id = shortcut.dev_kit_game_id.clone(); new_shortcut } _ => { diff --git a/src/sync/synchronization.rs b/src/sync/synchronization.rs index 7a996d1..22f0625 100644 --- a/src/sync/synchronization.rs +++ b/src/sync/synchronization.rs @@ -12,7 +12,7 @@ use crate::{ Collection, ShortcutInfo, SteamUsersInfo, }, steamgriddb::{download_images_for_users, ImageType}, - uplay::Uplay, + uplay::Uplay, }; #[cfg(target_family = "unix")] @@ -25,27 +25,24 @@ use std::{fs::File, io::Write, path::Path}; const BOILR_TAG: &str = "boilr"; - -pub enum SyncProgress{ +pub enum SyncProgress { NotStarted, Starting, - FoundGames{ - games_found:usize - }, + FoundGames { games_found: usize }, FindingImages, - DownloadingImages{ - to_download:usize, - }, - Done + DownloadingImages { to_download: usize }, + Done, } - -pub fn run_sync(settings: &Settings, sender: &mut Option>) -> Result, String> { - if let Some(sender) = &sender{ +pub fn run_sync( + settings: &Settings, + sender: &mut Option>, +) -> Result, String> { + if let Some(sender) = &sender { let _ = sender.send(SyncProgress::Starting); } let mut userinfo_shortcuts = get_shortcuts_paths(&settings.steam) - .map_err(|e| format!("Getting shortcut paths failed: {e}"))?; + .map_err(|e| format!("Getting shortcut paths failed: {e}"))?; let platform_shortcuts = get_platform_shortcuts(settings); let all_shortcuts: Vec = platform_shortcuts @@ -53,8 +50,10 @@ pub fn run_sync(settings: &Settings, sender: &mut Option>) .flat_map(|s| s.1.clone()) .filter(|s| !settings.blacklisted_games.contains(&s.app_id)) .collect(); - if let Some(sender) = &sender{ - let _ = sender.send(SyncProgress::FoundGames { games_found: all_shortcuts.len() }); + if let Some(sender) = &sender { + let _ = sender.send(SyncProgress::FoundGames { + games_found: all_shortcuts.len(), + }); } for shortcut in &all_shortcuts { println!("Appid: {} name: {}", shortcut.app_id, shortcut.app_name); @@ -91,33 +90,36 @@ pub fn run_sync(settings: &Settings, sender: &mut Option>) let duration = start_time.elapsed(); println!("Finished synchronizing games in: {:?}", duration); - } + } Ok(userinfo_shortcuts) } -pub async fn download_images(settings: &Settings, userinfo_shortcuts: &Vec,sender: &mut Option>) { +pub async fn download_images( + settings: &Settings, + userinfo_shortcuts: &[SteamUsersInfo], + sender: &mut Option>, +) { if settings.steamgrid_db.enabled { if settings.steamgrid_db.prefer_animated { println!("downloading animated images"); - download_images_for_users(settings, userinfo_shortcuts, true,sender).await; + download_images_for_users(settings, userinfo_shortcuts, true, sender).await; } - download_images_for_users(settings, userinfo_shortcuts, false,sender).await; + download_images_for_users(settings, userinfo_shortcuts, false, sender).await; } } -trait IsBoilRShortcut{ - fn is_boilr_tag(&self) -> bool; +trait IsBoilRShortcut { + fn is_boilr_tag(&self) -> bool; } -impl IsBoilRShortcut for ShortcutOwned{ +impl IsBoilRShortcut for ShortcutOwned { fn is_boilr_tag(&self) -> bool { let boilr_tag = BOILR_TAG.to_string(); self.tags.contains(&boilr_tag) || self.dev_kit_game_id.starts_with(&boilr_tag) } } - fn remove_old_shortcuts(shortcut_info: &mut ShortcutInfo) { shortcut_info .shortcuts @@ -138,15 +140,20 @@ fn fix_shortcut_icons( ImageType::Icon }; - for shortcut in shortcuts { - if shortcut.is_boilr_tag(){ - let replace_icon = shortcut.icon.trim().eq("") || !Path::new(shortcut.icon.trim()).exists() || shortcut.icon.eq(&shortcut.exe); + for shortcut in shortcuts { + if shortcut.is_boilr_tag() { + let replace_icon = shortcut.icon.trim().eq("") + || !Path::new(shortcut.icon.trim()).exists() + || shortcut.icon.eq(&shortcut.exe); if replace_icon { let app_id = steam_shortcuts_util::app_id_generator::calculate_app_id( &shortcut.exe, &shortcut.app_name, ); - shortcut.icon = image_folder.join(image_type.file_name(app_id)).to_string_lossy().to_string(); + shortcut.icon = image_folder + .join(image_type.file_name(app_id)) + .to_string_lossy() + .to_string(); } } } @@ -194,7 +201,7 @@ pub fn get_platform_shortcuts(settings: &Settings) -> Vec<(String, Vec Option { + if path.exists() { + if let Ok(data) = std::fs::read(path) { + return load_image_from_memory(&data).ok(); + } + } + None + } fn load_image_from_memory(image_data: &[u8]) -> Result { let image = image::load_from_memory(image_data)?; diff --git a/src/ui/mod.rs b/src/ui/mod.rs index ebd1c74..1c2b6d0 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,4 +1,11 @@ -mod uiapp; mod defines; -pub use uiapp::*; +mod ui_image_download; +mod ui_import_games; +mod ui_settings; +mod uiapp; + pub use defines::*; +pub use ui_image_download::*; +pub use ui_import_games::*; +pub use ui_settings::*; +pub use uiapp::*; diff --git a/src/ui/ui_image_download.rs b/src/ui/ui_image_download.rs new file mode 100644 index 0000000..66885f5 --- /dev/null +++ b/src/ui/ui_image_download.rs @@ -0,0 +1,409 @@ +use std::path::{Path, PathBuf}; + +use crate::{ + steam::{get_shortcuts_paths, SteamUsersInfo}, + steamgriddb::{CachedSearch, ImageType, get_query_type, ToDownload}, +}; +use dashmap::DashMap; +use egui::{ImageButton, ScrollArea}; +use futures::executor::block_on; +use steam_shortcuts_util::shortcut::ShortcutOwned; +use tokio::sync::watch::{self, Receiver}; + +use super::{ui_images::load_image_from_path, FetcStatus, MyEguiApp}; + +pub struct ImageSelectState { + pub selected_image: Option, + pub grid_id: Option, + + pub hero_image: Option, + pub grid_image: Option, + pub logo_image: Option, + pub icon_image: Option, + pub wide_image: Option, + + pub steam_user: Option, + pub steam_users: Option>, + + pub image_to_replace: Option, + pub image_options: Receiver>>, + + pub image_handles: DashMap, +} + +#[derive(Clone)] +pub struct PossibleImage{ + thumbnail_path: PathBuf, + full_url: String +} + +impl Default for ImageSelectState { + fn default() -> Self { + Self { + selected_image: Default::default(), + grid_id: Default::default(), + hero_image: Default::default(), + grid_image: Default::default(), + logo_image: Default::default(), + icon_image: Default::default(), + wide_image: Default::default(), + steam_user: Default::default(), + steam_users: Default::default(), + image_to_replace: Default::default(), + image_options: watch::channel(FetcStatus::NeedsFetched).1, + image_handles: DashMap::new() + } + } +} + +impl MyEguiApp { + pub(crate) fn render_ui_images(&mut self, ui: &mut egui::Ui) { + self.ensure_games_loaded(); + + ui.heading("Images"); + + match &self.image_selected_state.steam_user { + Some(user) => { + if self.image_selected_state.selected_image.is_some(){ + if ui.button("Back").clicked() { + if self.image_selected_state.image_to_replace.is_some(){ + self.image_selected_state.image_to_replace = None; + }else{ + self.image_selected_state.selected_image = None; + } + return; + } + } + + ScrollArea::vertical() + .stick_to_right() + .auto_shrink([false, true]) + .show(ui, |ui| { + ui.reset_style(); + let borrowed_games = &*self.games_to_sync.borrow(); + match borrowed_games { + super::FetcStatus::Fetched(games_to_sync) => { + match &self.image_selected_state.selected_image { + Some(selected_image) => { + + ui.heading(&selected_image.app_name); + let mut reset = false; + if let Some(selected_image_type) = + &self.image_selected_state.image_to_replace + { + let borrowed_images = + &*self.image_selected_state.image_options.borrow(); + match borrowed_images { + FetcStatus::Fetched(images) => { + for image in images{ + let image_key = image.thumbnail_path.as_path().to_string_lossy().to_string(); + if ! self.image_selected_state.image_handles.contains_key(&image_key){ + //TODO remove this unwrap + let image_data = load_image_from_path(&image.thumbnail_path).unwrap(); + let handle = ui.ctx().load_texture(&image_key, image_data); + self.image_selected_state.image_handles.insert(image_key.clone(),handle); + } + if let Some(texture_handle) = self.image_selected_state.image_handles.get(&image_key){ + let mut size = texture_handle.size_vec2(); + clamp_to_width(&mut size,MAX_WIDTH); + let image_button = ImageButton::new(texture_handle.value(), size); + if ui.add(image_button).clicked(){ + let to = + Path::new(&user.steam_user_data_folder) + .join("config") + .join("grid") + .join(selected_image_type.file_name(selected_image.app_id)); + let app_name = selected_image.app_name.clone(); + + let to_download = ToDownload{ + path: to, + url: image.full_url.clone(), + app_name: app_name.clone(), + image_type: selected_image_type.clone() + }; + //TODO make this actually parallel + self.rt.spawn_blocking(move ||{ + let _ = block_on(crate::steamgriddb::download_to_download(&to_download)); + }); + + let image_ref = match selected_image_type { + ImageType::Hero => { + &mut self.image_selected_state.hero_image + } + ImageType::Grid => { + &mut self.image_selected_state.grid_image + } + ImageType::WideGrid => { + &mut self.image_selected_state.wide_image + } + ImageType::Logo => { + &mut self.image_selected_state.logo_image + } + ImageType::BigPicture => { + &mut self.image_selected_state.wide_image + } + ImageType::Icon => { + &mut self.image_selected_state.icon_image + } + }; + *image_ref = Some(texture_handle.clone()); + reset = true; + } + } + } + }, + _ => { + ui.label("Finding possible images"); + }, + + } + } else { + if let Some(grid_id) = self.image_selected_state.grid_id + { + ui.horizontal(|ui| { + ui.label("Grid id:"); + let mut text_id = format!("{}", grid_id); + if ui + .text_edit_singleline(&mut text_id) + .changed() + { + if let Ok(grid_id) = + text_id.parse::() + { + if let Some(auth_key) = + &self.settings.steamgrid_db.auth_key + { + let client = + steamgriddb_api::Client::new( + auth_key, + ); + let mut search = + CachedSearch::new(&client); + search.set_cache( + selected_image.app_id, + selected_image + .app_name + .to_string(), + grid_id, + ); + } + self.image_selected_state.grid_id = + Some(grid_id); + } + }; + }); + } + + for image_type in ImageType::all() { + ui.label(image_type.name()); + + let image_ref = match image_type { + ImageType::Hero => { + &mut self.image_selected_state.hero_image + } + ImageType::Grid => { + &mut self.image_selected_state.grid_image + } + ImageType::WideGrid => { + &mut self.image_selected_state.wide_image + } + ImageType::Logo => { + &mut self.image_selected_state.logo_image + } + ImageType::BigPicture => { + &mut self.image_selected_state.wide_image + } + ImageType::Icon => { + &mut self.image_selected_state.icon_image + } + }; + if render_image(ui, image_ref) { + self.image_selected_state.image_to_replace = + Some(image_type.clone()); + let (mut tx,rx)= watch::channel(FetcStatus::Fetching); + self.image_selected_state.image_options = rx; + let settings = self.settings.clone(); + if let Some(auth_key) = settings.steamgrid_db.auth_key{ + if let Some(grid_id) = self.image_selected_state.grid_id{ + let auth_key = auth_key.clone(); + let image_type = image_type.clone(); + let app_name = selected_image.app_name.clone(); + self.rt.spawn_blocking( move|| { + //Find somewhere else to put this + std::fs::create_dir_all(".thumbnails"); + let thumbnails_folder = Path::new(".thumbnails"); + let client =steamgriddb_api::Client::new(auth_key); + let query = get_query_type(false,&image_type); + let search_res = block_on(client.get_images_for_id(grid_id, &query)); + + if let Ok(possible_images) = search_res{ + let mut result = vec![]; + for possible_image in &possible_images{ + let path = thumbnails_folder.join(format!("{}.png",possible_image.id)); + + if !&path.exists(){ + let to_download = ToDownload{ + path: path.clone(), + url: possible_image.thumb.clone(), + app_name: app_name.clone(), + image_type: image_type.clone() + }; + //TODO make this actually parallel + block_on(crate::steamgriddb::download_to_download(&to_download)); + } + result.push(PossibleImage { thumbnail_path: path, full_url: possible_image.url.clone() }); + let _ = tx.send(FetcStatus::Fetched(result.clone())); + } + } + }); + } + }; + } + } + } + if reset { + self.image_selected_state.image_to_replace = None; + self.image_selected_state.image_options = watch::channel(FetcStatus::NeedsFetched).1; + self.image_selected_state.image_handles.clear(); + } + } + None => { + for (platform_name, shortcuts) in games_to_sync { + ui.heading(platform_name); + for shortcut in shortcuts { + if ui.button(&shortcut.app_name).clicked() { + if let Some(auth_key) = + &self.settings.steamgrid_db.auth_key + { + let client = + steamgriddb_api::Client::new(auth_key); + let search = CachedSearch::new(&client); + //TODO make this multithreaded + self.image_selected_state.grid_id = self + .rt + .block_on(search.search( + shortcut.app_id, + &shortcut.app_name, + )) + .ok() + .flatten(); + } + + self.image_selected_state.selected_image = + Some(shortcut.clone()); + + let folder = + Path::new(&user.steam_user_data_folder) + .join("config") + .join("grid"); + + //TODO put this in seperate thread + self.image_selected_state.hero_image = + get_image( + ui, + shortcut, + &folder, + &ImageType::Hero, + ); + self.image_selected_state.grid_image = + get_image( + ui, + shortcut, + &folder, + &ImageType::Grid, + ); + self.image_selected_state.icon_image = + get_image( + ui, + shortcut, + &folder, + &ImageType::Icon, + ); + self.image_selected_state.logo_image = + get_image( + ui, + shortcut, + &folder, + &ImageType::Logo, + ); + self.image_selected_state.wide_image = + get_image( + ui, + shortcut, + &folder, + &ImageType::WideGrid, + ); + }; + } + } + } + } + } + _ => { + ui.label("Finding installed games"); + } + } + }); + } + None => { + let users = self + .image_selected_state + .steam_users + .get_or_insert_with(|| { + get_shortcuts_paths(&self.settings.steam).expect("Should have steam user") + }); + if users.len() == 1{ + self.image_selected_state.steam_user = Some(users[0].clone()) + } + for user in users { + if ui.button(&user.user_id).clicked() { + self.image_selected_state.steam_user = Some(user.clone()); + } + } + } + } + } +} + +const MAX_WIDTH:f32 = 300.; + +fn render_image(ui: &mut egui::Ui, image: &mut Option) -> bool { + match image { + Some(texture) => { + let mut size = texture.size_vec2(); + clamp_to_width(&mut size,MAX_WIDTH); + let image_button = ImageButton::new(texture, size); + ui.add(image_button) + .on_hover_text("Click to change image") + .clicked() + } + None => ui.button("Pick an image").clicked(), + } +} + +fn clamp_to_width(size: &mut egui::Vec2, max_width :f32) { + let mut x = size.x; + let mut y = size.y; + if size.x > max_width{ + let ratio = size.y / size.x; + x = max_width; + y = x * ratio; + } + size.x = x; + size.y = y; +} + +fn get_image( + ui: &mut egui::Ui, + shortcut: &ShortcutOwned, + folder: &std::path::Path, + image_type: &ImageType, +) -> Option { + let file_name = ImageType::file_name(image_type, shortcut.app_id); + let file_path = folder.join(file_name); + let image = load_image_from_path(file_path.as_path()).map(|img_data| { + ui.ctx() + .load_texture(file_path.to_string_lossy().to_string(), img_data) + }); + image +} diff --git a/src/ui/ui_import_games.rs b/src/ui/ui_import_games.rs new file mode 100644 index 0000000..1cb873d --- /dev/null +++ b/src/ui/ui_import_games.rs @@ -0,0 +1,133 @@ +use eframe::egui; +use egui::ScrollArea; +use futures::executor::block_on; + +use tokio::sync::watch; + +use crate::settings::Settings; +use crate::sync; + +use crate::sync::{download_images, SyncProgress}; + +use super::ImageSelectState; +use super::{ + ui_colors::{BACKGROUND_COLOR, EXTRA_BACKGROUND_COLOR}, + MyEguiApp, +}; + +const SECTION_SPACING: f32 = 25.0; + +pub enum FetcStatus { + NeedsFetched, + Fetching, + Fetched(T), +} + +impl FetcStatus { + pub fn is_some(&self) -> bool { + match self { + FetcStatus::NeedsFetched => false, + FetcStatus::Fetching => false, + FetcStatus::Fetched(_) => true, + } + } + + pub fn needs_fetching(&self) -> bool { + match self { + FetcStatus::NeedsFetched => true, + FetcStatus::Fetching => false, + FetcStatus::Fetched(_) => false, + } + } +} + +impl MyEguiApp { + pub(crate) fn render_import_games(&mut self, ui: &mut egui::Ui) { + ui.heading("Import Games"); + + self.ensure_games_loaded(); + + let mut scroll_style = ui.style_mut(); + scroll_style.visuals.extreme_bg_color = BACKGROUND_COLOR; + scroll_style.visuals.widgets.inactive.bg_fill = EXTRA_BACKGROUND_COLOR; + scroll_style.visuals.widgets.active.bg_fill = EXTRA_BACKGROUND_COLOR; + scroll_style.visuals.selection.bg_fill = EXTRA_BACKGROUND_COLOR; + scroll_style.visuals.widgets.hovered.bg_fill = EXTRA_BACKGROUND_COLOR; + + ScrollArea::vertical() + .stick_to_right() + .auto_shrink([false,true]) + .show(ui,|ui| { + ui.reset_style(); + + let borrowed_games = &*self.games_to_sync.borrow(); + match borrowed_games{ + FetcStatus::Fetched(games_to_sync) => { + ui.label("Select the games you want to import into steam"); + for (platform_name, shortcuts) in games_to_sync{ + ui.heading(platform_name); + for shortcut in shortcuts { + let mut import_game = !self.settings.blacklisted_games.contains(&shortcut.app_id); + let checkbox = egui::Checkbox::new(&mut import_game,&shortcut.app_name); + let response = ui.add(checkbox); + if response.clicked(){ + if !self.settings.blacklisted_games.contains(&shortcut.app_id){ + self.settings.blacklisted_games.push(shortcut.app_id); + }else{ + self.settings.blacklisted_games.retain(|id| *id != shortcut.app_id); + } + } + } + } + ui.add_space(SECTION_SPACING); + ui.label("Check the settings if BoilR didn't find the game you where looking for"); + }, + _=> { + ui.label("Finding installed games"); + }, + }; + }); + } + + pub fn ensure_games_loaded(&mut self) { + if self.games_to_sync.borrow().needs_fetching() { + self.image_selected_state = ImageSelectState::default(); + let (tx, rx) = watch::channel(FetcStatus::NeedsFetched); + self.games_to_sync = rx; + let settings = self.settings.clone(); + self.rt.spawn_blocking(move || { + let _ = tx.send(FetcStatus::Fetching); + let games_to_sync = sync::get_platform_shortcuts(&settings); + let _ = tx.send(FetcStatus::Fetched(games_to_sync)); + }); + } + } + + pub fn run_sync(&mut self) { + let (sender, reciever) = watch::channel(SyncProgress::NotStarted); + let settings = self.settings.clone(); + if settings.steam.stop_steam { + crate::steam::ensure_steam_stopped(); + } + + self.status_reciever = reciever; + self.rt.spawn_blocking(move || { + MyEguiApp::save_settings_to_file(&settings); + let mut some_sender = Some(sender); + let usersinfo = sync::run_sync(&settings, &mut some_sender).unwrap(); + let task = download_images(&settings, &usersinfo, &mut some_sender); + block_on(task); + if let Some(sender) = some_sender { + let _ = sender.send(SyncProgress::Done); + } + if settings.steam.start_steam { + crate::steam::ensure_steam_started(&settings.steam); + } + }); + } + + fn save_settings_to_file(settings: &Settings) { + let toml = toml::to_string(&settings).unwrap(); + std::fs::write("config.toml", toml).unwrap(); + } +} diff --git a/src/ui/ui_settings.rs b/src/ui/ui_settings.rs new file mode 100644 index 0000000..6c43d5d --- /dev/null +++ b/src/ui/ui_settings.rs @@ -0,0 +1,277 @@ +use eframe::egui; +use egui::ScrollArea; + +use crate::egs::EpicPlatform; + +use super::{ + ui_colors::{BACKGROUND_COLOR, EXTRA_BACKGROUND_COLOR}, + MyEguiApp, +}; +const SECTION_SPACING: f32 = 25.0; + +impl MyEguiApp { + pub(crate) fn render_settings(&mut self, ui: &mut egui::Ui) { + ui.heading("Settings"); + + let mut scroll_style = ui.style_mut(); + scroll_style.visuals.extreme_bg_color = BACKGROUND_COLOR; + scroll_style.visuals.widgets.inactive.bg_fill = EXTRA_BACKGROUND_COLOR; + scroll_style.visuals.widgets.active.bg_fill = EXTRA_BACKGROUND_COLOR; + scroll_style.visuals.selection.bg_fill = EXTRA_BACKGROUND_COLOR; + scroll_style.visuals.widgets.hovered.bg_fill = EXTRA_BACKGROUND_COLOR; + + ScrollArea::vertical() + .stick_to_right() + .auto_shrink([false, true]) + .show(ui, |ui| { + ui.reset_style(); + + self.render_steamgriddb_settings(ui); + + self.render_steam_settings(ui); + + self.render_epic_settings(ui); + + #[cfg(target_family = "unix")] + { + ui.heading("Heroic"); + ui.checkbox(&mut self.settings.heroic.enabled, "Import form Heroic"); + + ui.add_space(SECTION_SPACING); + } + + self.render_legendary_settings(ui); + self.render_itch_settings(ui); + self.render_origin_settings(ui); + self.render_gog_settings(ui); + self.render_uplay_settings(ui); + self.render_lutris_settings(ui); + #[cfg(windows)] + { + self.render_amazon_settings(ui); + } + }); + } + + fn render_lutris_settings(&mut self, ui: &mut egui::Ui) { + ui.heading("Lutris"); + ui.checkbox(&mut self.settings.lutris.enabled, "Import form Lutris"); + if self.settings.lutris.enabled { + ui.horizontal(|ui| { + let mut empty_string = "".to_string(); + let lutris_location = self + .settings + .lutris + .executable + .as_mut() + .unwrap_or(&mut empty_string); + ui.label("Lutris Location: "); + if ui.text_edit_singleline(lutris_location).changed() { + self.settings.lutris.executable = Some(lutris_location.to_string()); + } + }); + } + ui.add_space(SECTION_SPACING); + } + + #[cfg(windows)] + fn render_amazon_settings(&mut self, ui: &mut egui::Ui) { + ui.heading("Amazon"); + ui.checkbox(&mut self.settings.amazon.enabled, "Import form Amazon"); + ui.add_space(SECTION_SPACING); + } + + fn render_uplay_settings(&mut self, ui: &mut egui::Ui) { + ui.heading("Uplay"); + ui.checkbox(&mut self.settings.uplay.enabled, "Import form Uplay"); + ui.add_space(SECTION_SPACING); + } + + fn render_gog_settings(&mut self, ui: &mut egui::Ui) { + ui.heading("GoG Galaxy"); + ui.checkbox(&mut self.settings.gog.enabled, "Import form GoG Galaxy"); + if self.settings.gog.enabled { + ui.horizontal(|ui| { + let mut empty_string = "".to_string(); + let itch_location = self + .settings + .gog + .location + .as_mut() + .unwrap_or(&mut empty_string); + ui.label("GoG Galaxy Folder: "); + if ui.text_edit_singleline(itch_location).changed() { + self.settings.gog.location = Some(itch_location.to_string()); + } + }); + } + ui.add_space(SECTION_SPACING); + } + + fn render_origin_settings(&mut self, ui: &mut egui::Ui) { + ui.heading("Origin"); + ui.checkbox(&mut self.settings.origin.enabled, "Import from Origin"); + ui.add_space(SECTION_SPACING); + } + + fn render_itch_settings(&mut self, ui: &mut egui::Ui) { + ui.heading("Itch.io"); + ui.checkbox(&mut self.settings.itch.enabled, "Import form Itch.io"); + if self.settings.itch.enabled { + ui.horizontal(|ui| { + let mut empty_string = "".to_string(); + let itch_location = self + .settings + .itch + .location + .as_mut() + .unwrap_or(&mut empty_string); + ui.label("Itch.io Folder: "); + if ui.text_edit_singleline(itch_location).changed() { + self.settings.itch.location = Some(itch_location.to_string()); + } + }); + } + ui.add_space(SECTION_SPACING); + } + + fn render_steam_settings(&mut self, ui: &mut egui::Ui) { + ui.heading("Steam"); + ui.horizontal(|ui| { + let mut empty_string = "".to_string(); + let steam_location = self + .settings + .steam + .location + .as_mut() + .unwrap_or(&mut empty_string); + ui.label("Steam Location: "); + if ui.text_edit_singleline(steam_location).changed() { + self.settings.steam.location = Some(steam_location.to_string()); + } + }); + ui.checkbox( + &mut self.settings.steam.create_collections, + "Create collections", + ) + .on_hover_text("Tries to create a games collection for each platform"); + ui.checkbox(&mut self.settings.steam.optimize_for_big_picture, "Optimize for big picture").on_hover_text("Set icons to be larger horizontal images, this looks nice in steam big picture mode, but a bit off in desktop mode"); + ui.checkbox( + &mut self.settings.steam.stop_steam, + "Stop Steam before import", + ) + .on_hover_text("Stops Steam if it is running when import starts"); + ui.checkbox( + &mut self.settings.steam.start_steam, + "Start Steam after import", + ) + .on_hover_text("Starts Steam is it is not running after the import"); + ui.add_space(SECTION_SPACING); + } + + fn render_steamgriddb_settings(&mut self, ui: &mut egui::Ui) { + ui.heading("SteamGridDB"); + ui.checkbox(&mut self.settings.steamgrid_db.enabled, "Download images"); + if self.settings.steamgrid_db.enabled { + ui.horizontal(|ui| { + let mut empty_string = "".to_string(); + let auth_key = self + .settings + .steamgrid_db + .auth_key + .as_mut() + .unwrap_or(&mut empty_string); + ui.label("Authentication key: "); + if ui.text_edit_singleline(auth_key).changed() { + self.settings.steamgrid_db.auth_key = Some(auth_key.to_string()); + } + }); + ui.horizontal(|ui| { + ui.label( + "To download images you need an API Key from SteamGridDB, you can find yours", + ); + ui.hyperlink_to( + "here", + "https://www.steamgriddb.com/profile/preferences/api", + ) + }); + ui.checkbox(&mut self.settings.steamgrid_db.prefer_animated, "Prefer animated images").on_hover_text("Prefer downloading animated images over static images (this can slow Steam down but looks neat)"); + } + ui.add_space(SECTION_SPACING); + } + + fn render_legendary_settings(&mut self, ui: &mut egui::Ui) { + ui.heading("Legendary & Rare"); + ui.checkbox( + &mut self.settings.legendary.enabled, + "Import form Legendary & Rare", + ); + if self.settings.legendary.enabled { + ui.horizontal(|ui| { + let mut empty_string = "".to_string(); + let legendary_location = self + .settings + .legendary + .executable + .as_mut() + .unwrap_or(&mut empty_string); + ui.label("Legendary Executable: ") + .on_hover_text("The location of the legendary executable to use"); + if ui.text_edit_singleline(legendary_location).changed() { + self.settings.legendary.executable = Some(legendary_location.to_string()); + } + }); + } + ui.add_space(SECTION_SPACING); + } + + fn render_epic_settings(&mut self, ui: &mut egui::Ui) { + let epic_settings = &mut self.settings.epic_games; + ui.heading("Epic Games"); + ui.checkbox(&mut epic_settings.enabled, "Import form Epic Games"); + if epic_settings.enabled { + ui.horizontal(|ui| { + let mut empty_string = "".to_string(); + let epic_location = epic_settings.location.as_mut().unwrap_or(&mut empty_string); + ui.label("Epic Manifests Location: ").on_hover_text( + "The location where Epic stores its manifest files that BoilR needs to read", + ); + if ui.text_edit_singleline(epic_location).changed() { + epic_settings.location = Some(epic_location.to_string()); + } + }); + + let safe_mode_header = match epic_settings.safe_launch.len() { + 0 => "Force games to launch through Epic Launcher".to_string(), + 1 => "One game forced to launch through Epic Launcher".to_string(), + x => format!("{} games forced to launch through Epic Launcher", x), + }; + + egui::CollapsingHeader::new(safe_mode_header) + .id_source("Epic_Launcher_safe_launch") + .show(ui, |ui| { + ui.label("Some games must be started from the Epic Launcher, select those games below and BoilR will create shortcuts that opens the games through the Epic Launcher."); + let manifests =self.epic_manifests.get_or_insert_with(||{ + let epic_platform = EpicPlatform::new(epic_settings); + let manifests = crate::platform::Platform::get_shortcuts(&epic_platform); + manifests.unwrap_or_default() + }); + let mut safe_open_games = epic_settings.safe_launch.clone(); + for manifest in manifests{ + let key = manifest.get_key(); + let display_name = &manifest.display_name; + let mut safe_open = safe_open_games.contains(display_name) || safe_open_games.contains(&key); + if ui.checkbox(&mut safe_open, display_name).clicked(){ + if safe_open{ + safe_open_games.push(key); + }else{ + safe_open_games.retain(|m| m!= display_name && m!= &key); + } + } + } + epic_settings.safe_launch = safe_open_games; + }) ; + ui.add_space(SECTION_SPACING); + } + } +} diff --git a/src/ui/uiapp.rs b/src/ui/uiapp.rs index e0ec94b..2e1ccce 100644 --- a/src/ui/uiapp.rs +++ b/src/ui/uiapp.rs @@ -1,59 +1,44 @@ -use eframe::{egui, epi::{self},}; -use egui::{ScrollArea, TextureHandle, Stroke, Rounding, ImageButton}; -use futures::executor::block_on; +use std::error::Error; + +use eframe::{egui, epi}; +use egui::{ImageButton, Rounding, Stroke, TextureHandle}; use steam_shortcuts_util::shortcut::ShortcutOwned; -use std::{error::Error}; -use tokio::{runtime::Runtime, sync::watch::{Receiver, self}}; +use tokio::{ + runtime::Runtime, + sync::watch::{self, Receiver}, +}; -use crate::{settings::Settings, sync::{download_images, self, SyncProgress}, egs::{EpicPlatform, ManifestItem}}; +use crate::{egs::ManifestItem, settings::Settings, sync::SyncProgress}; -use super::{ui_images::{get_import_image, get_logo, get_logo_icon}, ui_colors::{TEXT_COLOR, BACKGROUND_COLOR, BG_STROKE_COLOR, ORANGE, PURLPLE, LIGHT_ORANGE, EXTRA_BACKGROUND_COLOR}}; +use super::{ + ui_colors::{ + BACKGROUND_COLOR, BG_STROKE_COLOR, EXTRA_BACKGROUND_COLOR, LIGHT_ORANGE, ORANGE, PURLPLE, + TEXT_COLOR, + }, + ui_images::{get_import_image, get_logo, get_logo_icon}, + ui_import_games::FetcStatus, + ImageSelectState, +}; -const SECTION_SPACING : f32 = 25.0; +const SECTION_SPACING: f32 = 25.0; #[derive(Default)] -struct UiImages{ +struct UiImages { import_button: Option, logo_32: Option, } - -enum FetchGameStatus{ - NeedsFetched, - Fetching, - Fetched(Vec<(String, Vec)>) -} - -impl FetchGameStatus{ - pub fn is_some(&self) -> bool{ - match self{ - FetchGameStatus::NeedsFetched => false, - FetchGameStatus::Fetching => false, - FetchGameStatus::Fetched(_) => true, - } - } - - pub fn needs_fetching(&self) -> bool{ - match self{ - FetchGameStatus::NeedsFetched => true, - FetchGameStatus::Fetching => false, - FetchGameStatus::Fetched(_) => false, - } - } -} - -struct MyEguiApp { +pub struct MyEguiApp { selected_menu: Menues, - settings: Settings, - rt: Runtime, - ui_images: UiImages, - games_to_sync: Receiver, - status_reciever: Receiver, - epic_manifests: Option>, + pub(crate) settings: Settings, + pub(crate) rt: Runtime, + ui_images: UiImages, + pub(crate) games_to_sync: Receiver)>>>, + pub(crate) status_reciever: Receiver, + pub(crate) epic_manifests: Option>, + pub(crate) image_selected_state: ImageSelectState, } - - impl MyEguiApp { pub fn new() -> Self { let runtime = Runtime::new().unwrap(); @@ -61,47 +46,20 @@ impl MyEguiApp { selected_menu: Menues::Import, settings: Settings::new().expect("We must be able to load our settings"), rt: runtime, - games_to_sync:watch::channel(FetchGameStatus::NeedsFetched).1, - ui_images: UiImages::default(), + games_to_sync: watch::channel(FetcStatus::NeedsFetched).1, + ui_images: UiImages::default(), status_reciever: watch::channel(SyncProgress::NotStarted).1, - epic_manifests : None, + epic_manifests: None, + image_selected_state: ImageSelectState::default(), } } - pub fn run_sync(&mut self) { - let (sender,reciever ) = watch::channel(SyncProgress::NotStarted); - let settings = self.settings.clone(); - if settings.steam.stop_steam{ - crate::steam::ensure_steam_stopped(); - } - - self.status_reciever = reciever; - self.rt.spawn_blocking(move || { - - MyEguiApp::save_settings_to_file(&settings); - let mut some_sender =Some(sender); - let usersinfo = sync::run_sync(&settings,&mut some_sender).unwrap(); - let task = download_images(&settings, &usersinfo,&mut some_sender); - block_on(task); - if let Some(sender) = some_sender{ - let _ = sender.send(SyncProgress::Done); - } - if settings.steam.start_steam{ - crate::steam::ensure_steam_started(&settings.steam); - } - }); - } - - - fn save_settings_to_file(settings: &Settings) { - let toml = toml::to_string(&settings).unwrap(); - std::fs::write("config.toml", toml).unwrap(); - } } #[derive(PartialEq)] enum Menues { - Import, - Settings, + Import, + Settings, + Images, } impl Default for Menues { @@ -113,22 +71,24 @@ impl Default for Menues { impl epi::App for MyEguiApp { fn name(&self) -> &str { "BoilR" - } + } fn setup( - &mut self, - ctx: &egui::Context, - _frame: &epi::Frame, - _storage: Option<&dyn epi::Storage> - ) { + &mut self, + ctx: &egui::Context, + _frame: &epi::Frame, + _storage: Option<&dyn epi::Storage>, + ) { ctx.set_pixels_per_point(1.0); let mut style: egui::Style = (*ctx.style()).clone(); create_style(&mut style); ctx.set_style(style); - } + } fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) { - let frame = egui::Frame::default().stroke(Stroke::new(0., BACKGROUND_COLOR)).fill(BACKGROUND_COLOR); + let frame = egui::Frame::default() + .stroke(Stroke::new(0., BACKGROUND_COLOR)) + .fill(BACKGROUND_COLOR); egui::SidePanel::new(egui::panel::Side::Left, "Side Panel") .default_width(40.0) .frame(frame) @@ -136,68 +96,72 @@ impl epi::App for MyEguiApp { let texture = self.get_logo_image(ui); let size = texture.size_vec2(); ui.image(texture, size); - ui.add_space(SECTION_SPACING); - - let changed = ui.selectable_value(&mut self.selected_menu, Menues::Import, "Import Games").changed(); - let changed = changed || ui.selectable_value(&mut self.selected_menu, Menues::Settings, "Settings").changed(); - if changed{ - self.games_to_sync =watch::channel(FetchGameStatus::NeedsFetched).1; + ui.add_space(SECTION_SPACING); + + let changed = ui + .selectable_value(&mut self.selected_menu, Menues::Import, "Import Games") + .changed(); + let changed = changed + || ui + .selectable_value(&mut self.selected_menu, Menues::Settings, "Settings") + .changed(); + let changed = changed + || ui + .selectable_value(&mut self.selected_menu, Menues::Images, "Images") + .changed(); + if changed && self.selected_menu == Menues::Settings { + //We reset games here, since user might change settings + self.games_to_sync = watch::channel(FetcStatus::NeedsFetched).1; } - }); - if self.games_to_sync.borrow().is_some(){ - + if self.games_to_sync.borrow().is_some() { egui::TopBottomPanel::new(egui::panel::TopBottomSide::Bottom, "Bottom Panel") - .frame(frame) - .show(ctx,|ui|{ - let (status_string,syncing) = match &*self.status_reciever.borrow(){ - SyncProgress::NotStarted => { - ("".to_string(),false) - }, - SyncProgress::Starting => { - ("Starting Import".to_string(),true) - }, + .frame(frame) + .show(ctx, |ui| { + let (status_string, syncing) = match &*self.status_reciever.borrow() { + SyncProgress::NotStarted => ("".to_string(), false), + SyncProgress::Starting => ("Starting Import".to_string(), true), SyncProgress::FoundGames { games_found } => { - (format!("Found {} games to import",games_found),true) - }, - SyncProgress::FindingImages => { - (format!("Searching for images"),true) - }, - SyncProgress::DownloadingImages { to_download } => { - (format!("Downloading {} images ",to_download),true) - }, - SyncProgress::Done => { - (format!("Done importing games"),false) - }, - }; - if status_string != "" { - ui.label(status_string); - } - - let texture = self.get_import_image(ui); - let size = texture.size_vec2(); - let image_button = ImageButton::new(texture, size * 0.5); - if ui.add(image_button).on_hover_text("Import your games into steam") - .clicked() && !syncing{ + (format!("Found {} games to import", games_found), true) + } + SyncProgress::FindingImages => ("Searching for images".to_string(), true), + SyncProgress::DownloadingImages { to_download } => { + (format!("Downloading {} images ", to_download), true) + } + SyncProgress::Done => ("Done importing games".to_string(), false), + }; + if !status_string.is_empty() { + ui.label(status_string); + } + + let texture = self.get_import_image(ui); + let size = texture.size_vec2(); + let image_button = ImageButton::new(texture, size * 0.5); + if ui + .add(image_button) + .on_hover_text("Import your games into steam") + .clicked() + && !syncing + { self.run_sync(); - } - }); + } + }); } - egui::CentralPanel::default() - .show(ctx, |ui| { - match self.selected_menu { - Menues::Import => { + egui::CentralPanel::default().show(ctx, |ui| { + match self.selected_menu { + Menues::Import => { self.render_import_games(ui); - - }, + } Menues::Settings => { - self.render_settings(ui); - }, + self.render_settings(ui); + } + Menues::Images => { + self.render_ui_images(ui); + } }; - - }); -} + }); + } } fn create_style(style: &mut egui::Style) { @@ -205,339 +169,60 @@ fn create_style(style: &mut egui::Style) { style.visuals.button_frame = false; style.visuals.dark_mode = true; style.visuals.override_text_color = Some(TEXT_COLOR); - style.visuals.widgets.noninteractive.rounding = Rounding{ - ne:0.0, - nw:0.0, - se:0.0, - sw:0.0 + style.visuals.widgets.noninteractive.rounding = Rounding { + ne: 0.0, + nw: 0.0, + se: 0.0, + sw: 0.0, }; style.visuals.faint_bg_color = PURLPLE; style.visuals.extreme_bg_color = EXTRA_BACKGROUND_COLOR; style.visuals.widgets.active.bg_fill = BACKGROUND_COLOR; - style.visuals.widgets.active.bg_stroke = Stroke::new(2.0,BG_STROKE_COLOR); - style.visuals.widgets.active.fg_stroke = Stroke::new(2.0,LIGHT_ORANGE); + style.visuals.widgets.active.bg_stroke = Stroke::new(2.0, BG_STROKE_COLOR); + style.visuals.widgets.active.fg_stroke = Stroke::new(2.0, LIGHT_ORANGE); style.visuals.widgets.open.bg_fill = BACKGROUND_COLOR; - style.visuals.widgets.open.bg_stroke = Stroke::new(2.0,BG_STROKE_COLOR); - style.visuals.widgets.open.fg_stroke = Stroke::new(2.0,LIGHT_ORANGE); + style.visuals.widgets.open.bg_stroke = Stroke::new(2.0, BG_STROKE_COLOR); + style.visuals.widgets.open.fg_stroke = Stroke::new(2.0, LIGHT_ORANGE); style.visuals.widgets.noninteractive.bg_fill = BACKGROUND_COLOR; - style.visuals.widgets.noninteractive.bg_stroke = Stroke::new(2.0,BG_STROKE_COLOR); - style.visuals.widgets.noninteractive.fg_stroke = Stroke::new(2.0,ORANGE); + style.visuals.widgets.noninteractive.bg_stroke = Stroke::new(2.0, BG_STROKE_COLOR); + style.visuals.widgets.noninteractive.fg_stroke = Stroke::new(2.0, ORANGE); style.visuals.widgets.inactive.bg_fill = BACKGROUND_COLOR; - style.visuals.widgets.inactive.bg_stroke = Stroke::new(2.0,BG_STROKE_COLOR); - style.visuals.widgets.inactive.fg_stroke = Stroke::new(2.0,ORANGE); + style.visuals.widgets.inactive.bg_stroke = Stroke::new(2.0, BG_STROKE_COLOR); + style.visuals.widgets.inactive.fg_stroke = Stroke::new(2.0, ORANGE); style.visuals.widgets.hovered.bg_fill = BACKGROUND_COLOR; - style.visuals.widgets.hovered.bg_stroke = Stroke::new(2.0,BG_STROKE_COLOR); - style.visuals.widgets.hovered.fg_stroke = Stroke::new(2.0,LIGHT_ORANGE); + style.visuals.widgets.hovered.bg_stroke = Stroke::new(2.0, BG_STROKE_COLOR); + style.visuals.widgets.hovered.fg_stroke = Stroke::new(2.0, LIGHT_ORANGE); style.visuals.selection.bg_fill = PURLPLE; } -impl MyEguiApp{ - - fn get_import_image(&mut self, ui:&mut egui::Ui) -> &mut TextureHandle { - self.ui_images.import_button.get_or_insert_with(|| { - // Load the texture only once. - ui.ctx().load_texture("import_image", get_import_image()) - }) - +impl MyEguiApp { + fn get_import_image(&mut self, ui: &mut egui::Ui) -> &mut TextureHandle { + self.ui_images.import_button.get_or_insert_with(|| { + // Load the texture only once. + ui.ctx().load_texture("import_image", get_import_image()) + }) } - fn get_logo_image(&mut self, ui:&mut egui::Ui) -> &mut TextureHandle { + fn get_logo_image(&mut self, ui: &mut egui::Ui) -> &mut TextureHandle { self.ui_images.logo_32.get_or_insert_with(|| { // Load the texture only once. ui.ctx().load_texture("logo32", get_logo()) }) - - } - - fn render_settings(&mut self, ui: &mut egui::Ui){ - ui.heading("Settings"); - - let mut scroll_style = ui.style_mut(); - scroll_style.visuals.extreme_bg_color = BACKGROUND_COLOR; - scroll_style.visuals.widgets.inactive.bg_fill = EXTRA_BACKGROUND_COLOR; - scroll_style.visuals.widgets.active.bg_fill = EXTRA_BACKGROUND_COLOR; - scroll_style.visuals.selection.bg_fill = EXTRA_BACKGROUND_COLOR; - scroll_style.visuals.widgets.hovered.bg_fill = EXTRA_BACKGROUND_COLOR; - - ScrollArea::vertical() - .stick_to_right() - .auto_shrink([false,true]) - .show(ui,|ui| { - ui.reset_style(); - - self.render_steamgriddb_settings(ui); - - self.render_steam_settings(ui); - - self.render_epic_settings(ui); - - - #[cfg(target_family = "unix")] - { - ui.heading("Heroic"); - ui.checkbox(&mut self.settings.heroic.enabled, "Import form Heroic"); - - ui.add_space(SECTION_SPACING); - } - - self.render_legendary_settings(ui); - self.render_itch_settings(ui); - self.render_origin_settings(ui); - self.render_gog_settings(ui); - self.render_uplay_settings(ui); - self.render_lutris_settings(ui); - #[cfg(windows)] - { - self.render_amazon_settings(ui); - } - - }); - } - - fn render_lutris_settings(&mut self, ui: &mut egui::Ui) { - ui.heading("Lutris"); - ui.checkbox(&mut self.settings.lutris.enabled, "Import form Lutris"); - if self.settings.lutris.enabled{ - ui.horizontal(|ui| { - let mut empty_string ="".to_string(); - let lutris_location = self.settings.lutris.executable.as_mut().unwrap_or(&mut empty_string); - ui.label("Lutris Location: "); - if ui.text_edit_singleline(lutris_location).changed(){ - self.settings.lutris.executable = Some(lutris_location.to_string()); - } - }); - } - ui.add_space(SECTION_SPACING); - } - - fn render_amazon_settings(&mut self, ui: &mut egui::Ui) { - ui.heading("Amazon"); - ui.checkbox(&mut self.settings.amazon.enabled, "Import form Amazon"); - ui.add_space(SECTION_SPACING); - } - - fn render_uplay_settings(&mut self, ui: &mut egui::Ui) { - ui.heading("Uplay"); - ui.checkbox(&mut self.settings.uplay.enabled, "Import form Uplay"); - ui.add_space(SECTION_SPACING); - } - - fn render_gog_settings(&mut self, ui: &mut egui::Ui) { - ui.heading("GoG Galaxy"); - ui.checkbox(&mut self.settings.gog.enabled, "Import form GoG Galaxy"); - if self.settings.gog.enabled { - ui.horizontal(|ui| { - let mut empty_string ="".to_string(); - let itch_location = self.settings.gog.location.as_mut().unwrap_or(&mut empty_string); - ui.label("GoG Galaxy Folder: "); - if ui.text_edit_singleline(itch_location).changed(){ - self.settings.gog.location = Some(itch_location.to_string()); - } - }); - } - ui.add_space(SECTION_SPACING); - } - - fn render_origin_settings(&mut self, ui: &mut egui::Ui) { - ui.heading("Origin"); - ui.checkbox(&mut self.settings.origin.enabled, "Import from Origin"); - ui.add_space(SECTION_SPACING); - } - - fn render_itch_settings(&mut self, ui: &mut egui::Ui) { - ui.heading("Itch.io"); - ui.checkbox(&mut self.settings.itch.enabled, "Import form Itch.io"); - if self.settings.itch.enabled { - ui.horizontal(|ui| { - let mut empty_string ="".to_string(); - let itch_location = self.settings.itch.location.as_mut().unwrap_or(&mut empty_string); - ui.label("Itch.io Folder: "); - if ui.text_edit_singleline(itch_location).changed(){ - self.settings.itch.location = Some(itch_location.to_string()); - } - }); - } - ui.add_space(SECTION_SPACING); - } - - fn render_steam_settings(&mut self, ui: &mut egui::Ui) { - ui.heading("Steam"); - ui.horizontal(|ui| { - let mut empty_string ="".to_string(); - let steam_location = self.settings.steam.location.as_mut().unwrap_or(&mut empty_string); - ui.label("Steam Location: "); - if ui.text_edit_singleline(steam_location).changed(){ - self.settings.steam.location = Some(steam_location.to_string()); - } - }); - ui.checkbox(&mut self.settings.steam.create_collections, "Create collections").on_hover_text("Tries to create a games collection for each platform"); - ui.checkbox(&mut self.settings.steam.optimize_for_big_picture, "Optimize for big picture").on_hover_text("Set icons to be larger horizontal images, this looks nice in steam big picture mode, but a bit off in desktop mode"); - ui.checkbox(&mut self.settings.steam.stop_steam, "Stop Steam before import").on_hover_text("Stops Steam if it is running when import starts"); - ui.checkbox(&mut self.settings.steam.start_steam, "Start Steam after import").on_hover_text("Starts Steam is it is not running after the import"); - ui.add_space(SECTION_SPACING); - } - - fn render_steamgriddb_settings(&mut self, ui: &mut egui::Ui) { - ui.heading("SteamGridDB"); - ui.checkbox(&mut self.settings.steamgrid_db.enabled, "Download images"); - if self.settings.steamgrid_db.enabled{ - ui.horizontal(|ui| { - let mut empty_string ="".to_string(); - let auth_key = self.settings.steamgrid_db.auth_key.as_mut().unwrap_or(&mut empty_string); - ui.label("Authentication key: "); - if ui.text_edit_singleline(auth_key).changed(){ - self.settings.steamgrid_db.auth_key = Some(auth_key.to_string()); - } - }); - ui.horizontal(|ui| { - ui.label("To download images you need an API Key from SteamGridDB, you can find yours"); - ui.hyperlink_to("here", "https://www.steamgriddb.com/profile/preferences/api") - }); - ui.checkbox(&mut self.settings.steamgrid_db.prefer_animated, "Prefer animated images").on_hover_text("Prefer downloading animated images over static images (this can slow Steam down but looks neat)"); - } - ui.add_space(SECTION_SPACING); - } - - fn render_legendary_settings(&mut self, ui: &mut egui::Ui) { - ui.heading("Legendary & Rare"); - ui.checkbox(&mut self.settings.legendary.enabled, "Import form Legendary & Rare"); - if self.settings.legendary.enabled{ - ui.horizontal(|ui| { - let mut empty_string ="".to_string(); - let legendary_location = self.settings.legendary.executable.as_mut().unwrap_or(&mut empty_string); - ui.label("Legendary Executable: ").on_hover_text("The location of the legendary executable to use"); - if ui.text_edit_singleline(legendary_location).changed(){ - self.settings.legendary.executable = Some(legendary_location.to_string()); - } - }); - } - ui.add_space(SECTION_SPACING); - } - - fn render_epic_settings(&mut self, ui: &mut egui::Ui) { - let epic_settings = &mut self.settings.epic_games; - ui.heading("Epic Games"); - ui.checkbox(&mut epic_settings.enabled, "Import form Epic Games"); - if epic_settings.enabled{ - ui.horizontal(|ui| { - let mut empty_string ="".to_string(); - let epic_location = epic_settings.location.as_mut().unwrap_or(&mut empty_string); - ui.label("Epic Manifests Location: ").on_hover_text("The location where Epic stores its manifest files that BoilR needs to read"); - if ui.text_edit_singleline(epic_location).changed(){ - epic_settings.location = Some(epic_location.to_string()); - } - }); - - let safe_mode_header = match epic_settings.safe_launch.len(){ - 0 => "Force games to launch through Epic Launcher".to_string(), - 1 => "One game forced to launch through Epic Launcher".to_string(), - x => format!("{} games forced to launch through Epic Launcher",x) - }; - - egui::CollapsingHeader::new(safe_mode_header) - .id_source("Epic_Launcher_safe_launch") - .show(ui, |ui| { - ui.label("Some games must be started from the Epic Launcher, select those games below and BoilR will create shortcuts that opens the games through the Epic Launcher."); - let manifests =self.epic_manifests.get_or_insert_with(||{ - let epic_platform = EpicPlatform::new(epic_settings); - let manifests = crate::platform::Platform::get_shortcuts(&epic_platform); - manifests.unwrap_or_default() - }); - - let mut safe_open_games = epic_settings.safe_launch.clone(); - for manifest in manifests{ - let key = manifest.get_key(); - let display_name = &manifest.display_name; - let mut safe_open = safe_open_games.contains(display_name) || safe_open_games.contains(&key); - if ui.checkbox(&mut safe_open, display_name).clicked(){ - if safe_open{ - safe_open_games.push(key); - }else{ - safe_open_games.retain(|m| m!= display_name && m!= &key); - } - } - } - epic_settings.safe_launch = safe_open_games; - }) ; - ui.add_space(SECTION_SPACING); } } - fn render_import_games(&mut self, ui: &mut egui::Ui){ - - ui.heading("Import Games"); - - if self.games_to_sync.borrow().needs_fetching(){ - let (tx, rx) = watch::channel(FetchGameStatus::NeedsFetched); - self.games_to_sync = rx; - let settings = self.settings.clone(); - self.rt.spawn_blocking(move || { - let _= tx.send(FetchGameStatus::Fetching); - let games_to_sync = sync::get_platform_shortcuts(&settings); - let _= tx.send(FetchGameStatus::Fetched(games_to_sync)); - }); - } - - let mut scroll_style = ui.style_mut(); - scroll_style.visuals.extreme_bg_color = BACKGROUND_COLOR; - scroll_style.visuals.widgets.inactive.bg_fill = EXTRA_BACKGROUND_COLOR; - scroll_style.visuals.widgets.active.bg_fill = EXTRA_BACKGROUND_COLOR; - scroll_style.visuals.selection.bg_fill = EXTRA_BACKGROUND_COLOR; - scroll_style.visuals.widgets.hovered.bg_fill = EXTRA_BACKGROUND_COLOR; - - - ScrollArea::vertical() - .stick_to_right() - .auto_shrink([false,true]) - .show(ui,|ui| { - ui.reset_style(); - - let borrowed_games = &*self.games_to_sync.borrow(); - match borrowed_games{ - FetchGameStatus::Fetched(games_to_sync) => { - ui.label("Select the games you want to import into steam"); - for (platform_name, shortcuts) in games_to_sync{ - ui.heading(platform_name); - for shortcut in shortcuts { - let mut import_game = !self.settings.blacklisted_games.contains(&shortcut.app_id); - let checkbox = egui::Checkbox::new(&mut import_game,&shortcut.app_name); - let response = ui.add(checkbox); - if response.clicked(){ - if !self.settings.blacklisted_games.contains(&shortcut.app_id){ - self.settings.blacklisted_games.push(shortcut.app_id); - }else{ - self.settings.blacklisted_games.retain(|id| *id != shortcut.app_id); - } - } - } - } - ui.add_space(SECTION_SPACING); - ui.label("Check the settings if BoilR didn't find the game you where looking for"); - }, - _=> { - ui.label("Finding installed games"); - }, - }; - - }); - } -} - - pub fn run_sync() { let mut app = MyEguiApp::new(); app.run_sync(); } - pub fn run_ui() -> Result<(), Box> { let app = MyEguiApp::new(); - let mut native_options = eframe::NativeOptions::default(); - native_options.initial_window_size = Some(egui::Vec2{ - x:800., - y:500. - }); - native_options.icon_data = Some(get_logo_icon()); + let native_options = eframe::NativeOptions { + initial_window_size: Some(egui::Vec2 { x: 800., y: 500. }), + icon_data: Some(get_logo_icon()), + ..Default::default() + }; eframe::run_native(Box::new(app), native_options); } diff --git a/src/uplay/platform.rs b/src/uplay/platform.rs index af2f395..915bc56 100644 --- a/src/uplay/platform.rs +++ b/src/uplay/platform.rs @@ -49,7 +49,7 @@ impl Platform> for Uplay { #[cfg(target_family = "unix")] { //TODO update this when uplay gets proton support on linux - return true; + true } } }