diff --git a/src/config.rs b/src/config.rs index de06410..a1665f3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -64,10 +64,10 @@ mod tests { // Parameters for set environment 'XDG_CONFIG_HOME' std::env::set_var( "XDG_CONFIG_HOME", - std::env::var("HOME").unwrap() + "/.config/boilr", + std::env::var("HOME").unwrap_or_default() + "/.config/boilr", ); - let xdg_config_home = std::env::var("XDG_CONFIG_HOME").unwrap(); + let xdg_config_home = std::env::var("XDG_CONFIG_HOME").unwrap_or_default(); let config_path = get_config_folder(); let test_path = PathBuf::from(xdg_config_home); @@ -79,11 +79,11 @@ mod tests { fn check_return_config_path() { std::env::set_var( "XDG_CONFIG_HOME", - std::env::var("HOME").unwrap() + "/.config/boilr", + std::env::var("HOME").unwrap_or_default() + "/.config/boilr", ); let config_path = get_config_folder(); - let current_path = std::env::var("HOME").unwrap() + "/.config/boilr"; + let current_path = std::env::var("HOME").unwrap_or_default() + "/.config/boilr"; assert_eq!(config_path, PathBuf::from(current_path)); } diff --git a/src/main.rs b/src/main.rs index 3937b64..7af565a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,7 @@ +#![deny(clippy::unwrap_in_result)] +#![deny(clippy::get_unwrap)] +#![deny(clippy::unwrap_used)] + mod config; mod migration; mod platforms; @@ -16,9 +20,9 @@ fn main() -> Result<()> { let args: Vec = std::env::args().collect(); if args.contains(&"--no-ui".to_string()) { - ui::run_sync(); + ui::run_sync()?; } else { - ui::run_ui(args); + ui::run_ui(args)?; } Ok(()) } diff --git a/src/migration.rs b/src/migration.rs index c7c5307..c438e1f 100644 --- a/src/migration.rs +++ b/src/migration.rs @@ -38,7 +38,9 @@ pub fn migrate_config() { if let Ok(mut settings) = crate::settings::Settings::new() { settings.config_version = Some(1); let platforms = get_platforms(); - save_settings(&settings, &platforms); + if let Err(err) = save_settings(&settings, &platforms){ + eprintln!("Failed to load settings {:?}", err); + } } } } diff --git a/src/platforms/egs/manifest_item.rs b/src/platforms/egs/manifest_item.rs index 1fc390c..e0e2e50 100644 --- a/src/platforms/egs/manifest_item.rs +++ b/src/platforms/egs/manifest_item.rs @@ -185,6 +185,9 @@ impl ManifestItem { #[cfg(test)] mod tests { + //Okay to unwrap in tests + #![allow(clippy::unwrap_in_result)] + #![allow(clippy::unwrap_used)] use super::*; #[test] diff --git a/src/platforms/gog/gog_game.rs b/src/platforms/gog/gog_game.rs index f702ca1..5b09042 100644 --- a/src/platforms/gog/gog_game.rs +++ b/src/platforms/gog/gog_game.rs @@ -42,7 +42,7 @@ impl From for ShortcutOwned { let icon_file = format!("goggame-{}.ico", gogs.game_id); let icon_path = Path::new(&gogs.game_folder).join(icon_file); let icon = if icon_path.exists() { - icon_path.to_str().unwrap().to_string() + icon_path.to_string_lossy().to_string() } else { "".to_string() }; diff --git a/src/platforms/itch/butler_db_parser.rs b/src/platforms/itch/butler_db_parser.rs index 5369766..9c110fe 100644 --- a/src/platforms/itch/butler_db_parser.rs +++ b/src/platforms/itch/butler_db_parser.rs @@ -62,6 +62,9 @@ fn parse_path(i: &[u8]) -> nom::IResult<&[u8], DbPaths> { #[cfg(test)] mod tests { + //Okay to unwrap in tests + #![allow(clippy::unwrap_in_result)] + #![allow(clippy::unwrap_used)] use super::*; diff --git a/src/platforms/itch/itch_game.rs b/src/platforms/itch/itch_game.rs index 0a8ea7c..f916a25 100644 --- a/src/platforms/itch/itch_game.rs +++ b/src/platforms/itch/itch_game.rs @@ -18,7 +18,7 @@ pub struct ItchGame { impl From for ShortcutOwned { fn from(game: ItchGame) -> Self { let exe = Path::new(&game.install_path).join(&game.executable); - let exe = exe.to_str().unwrap().to_string(); + let exe = exe.to_string_lossy().to_string(); let shortcut = Shortcut::new( "0", game.title.as_str(), diff --git a/src/platforms/itch/itch_platform.rs b/src/platforms/itch/itch_platform.rs index 914dea9..e70447a 100644 --- a/src/platforms/itch/itch_platform.rs +++ b/src/platforms/itch/itch_platform.rs @@ -29,7 +29,7 @@ impl ItchPlatform { )); } - let shortcut_bytes = std::fs::read(&itch_db_location).unwrap(); + let shortcut_bytes = std::fs::read(&itch_db_location)?; let paths = match parse_butler_db(&shortcut_bytes) { Ok((_, shortcuts)) => Ok(shortcuts), @@ -59,17 +59,19 @@ fn dbpath_to_game(paths: &DbPaths) -> Option { .iter() .filter(|p| Path::new(&paths.base_path).join(p).is_executable()) .find_map(|executable| { - let gz_bytes = std::fs::read(&recipt).unwrap(); - let mut d = GzDecoder::new(gz_bytes.as_slice()); - let mut s = String::new(); - d.read_to_string(&mut s).unwrap(); - - let receipt_op: Option = serde_json::from_str(&s).ok(); - receipt_op.map(|re| ItchGame { - install_path: paths.base_path.to_owned(), - executable: executable.to_owned(), - title: re.game.title, - }) + if let Ok(gz_bytes) = std::fs::read(&recipt) { + let mut d = GzDecoder::new(gz_bytes.as_slice()); + let mut s = String::new(); + if d.read_to_string(&mut s).is_ok() { + let receipt_op: Option = serde_json::from_str(&s).ok(); + return receipt_op.map(|re| ItchGame { + install_path: paths.base_path.to_owned(), + executable: executable.to_owned(), + title: re.game.title, + }); + } + } + None }) } diff --git a/src/platforms/origin/origin_platform.rs b/src/platforms/origin/origin_platform.rs index 7cce76c..5e98729 100644 --- a/src/platforms/origin/origin_platform.rs +++ b/src/platforms/origin/origin_platform.rs @@ -32,11 +32,7 @@ impl NeedsPorton for OriginGame { impl OriginPlatform { fn get_shortcuts(&self) -> eyre::Result> { - let origin_folders = get_default_locations(); - if origin_folders.is_none() { - return Err(eyre::format_err!("Default path not found")); - } - let origin_folders = origin_folders.unwrap(); + let origin_folders = get_default_locations().ok_or(eyre::format_err!("Default path not found"))?; let origin_folder = origin_folders.local_content_path; let origin_exe = origin_folders.exe_path; let game_folders = origin_folder.join("LocalContent").read_dir()?; diff --git a/src/settings.rs b/src/settings.rs index 3e5e884..293e40a 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -69,8 +69,8 @@ pub fn load_setting_sections() -> eyre::Result> { Ok(result) } -pub fn save_settings(settings: &Settings, platforms: &Platforms) { - let mut toml = toml::to_string(&settings).unwrap(); +pub fn save_settings(settings: &Settings, platforms: &Platforms) -> eyre::Result<()>{ + let mut toml = toml::to_string(&settings)?; for platform in platforms { let section_name = format!("[{}]", platform.code_name()); @@ -82,7 +82,8 @@ pub fn save_settings(settings: &Settings, platforms: &Platforms) { } let config_path = crate::config::get_config_file(); - std::fs::write(config_path, toml).unwrap(); + std::fs::write(config_path, toml)?; + Ok(()) } fn add_sections( diff --git a/src/steam/collections.rs b/src/steam/collections.rs index 9c7bd20..215cedb 100644 --- a/src/steam/collections.rs +++ b/src/steam/collections.rs @@ -158,7 +158,7 @@ pub fn write_collections>( &vdf_collections, ); if let Some(new_string) = new_string { - std::fs::write(path, new_string).unwrap(); + std::fs::write(path, new_string)?; } } } @@ -242,22 +242,19 @@ fn get_categories>( Ok(res) } -fn open_db() -> Result> { - let location = get_level_db_location(); +fn open_db() -> eyre::Result { + use eyre::eyre; + let location = get_level_db_location().ok_or(eyre!("Collections db not found"))?; let options = Options::default(); - let open_res = DB::open(location.unwrap(), options); - if let Err(e) = &open_res { - match &e.code { - rusty_leveldb::StatusCode::LockError => { - println!("Could not lock the steam level database, make sure steam is turned off when running synchronizations"); - } - rusty_leveldb::StatusCode::NotFound => { - println!("Could not find the steam level database, try to open and close steam once and synchronize again"); - } - _ => {} - }; - } - Ok(open_res?) + let open_res = DB::open(location, options); + open_res.map_err(|e|{ + use rusty_leveldb::StatusCode::*; + match e.code{ + LockError => eyre!("Could not lock the steam level database, make sure steam is turned off when running synchronizations"), + NotFound => eyre!("Could not find the steam level database, try to open and close steam once and synchronize again"), + _ => eyre!("Failed opening collections file: {}",e.err), + } + }) } fn get_namespace_keys>(steamid: S, db: &mut DB) -> HashSet { @@ -394,6 +391,10 @@ pub struct VdfCollection { #[cfg(test)] mod tests { + //Allow unwraps in test + #![allow(clippy::unwrap_in_result)] + #![allow(clippy::get_unwrap)] + #![allow(clippy::unwrap_used)] use super::*; #[test] diff --git a/src/steam/installed_games.rs b/src/steam/installed_games.rs index c671ed7..269a504 100644 --- a/src/steam/installed_games.rs +++ b/src/steam/installed_games.rs @@ -66,19 +66,25 @@ fn parse_manifest_string>(string: S) -> Option { let app_id_line = lines.find(|l| l.contains("\"appid\"")); let name_line = lines.find(|l| l.contains("\"name\"")); match (app_id_line, name_line) { - (Some(app_id_line), Some(name_line)) => Some(SteamGameInfo { - name: name_line[10..name_line.len() - 1].to_string(), - appid: app_id_line[11..app_id_line.len() - 1] - .to_string() - .parse() - .unwrap(), - }), + (Some(app_id_line), Some(name_line)) => { + let appid = app_id_line[11..app_id_line.len() - 1].to_string().parse(); + match appid { + Ok(appid) => Some(SteamGameInfo { + name: name_line[10..name_line.len() - 1].to_string(), + appid, + }), + Err(_) => None, + } + } _ => None, } } #[cfg(test)] mod tests { + //Okay to unwrap in tests + #![allow(clippy::unwrap_in_result)] + #![allow(clippy::unwrap_used)] use super::*; diff --git a/src/steam/proton_vdf_util.rs b/src/steam/proton_vdf_util.rs index 17353b1..49a2613 100644 --- a/src/steam/proton_vdf_util.rs +++ b/src/steam/proton_vdf_util.rs @@ -2,16 +2,17 @@ use std::path::Path; use nom::FindSubstring; -pub fn setup_proton_games>(games: &[B]) { +pub fn setup_proton_games>(games: &[B]) -> eyre::Result<()>{ 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) { let new_string = enable_proton_games(config_content, games); - std::fs::write(config_file, new_string).unwrap(); + std::fs::write(config_file, new_string)?; } } } + Ok(()) } fn enable_proton_games, B: AsRef>(vdf_content: S, games: &[B]) -> String { @@ -96,6 +97,9 @@ fn find_indexes>(vdf_content: S) -> Option { #[cfg(target_family = "unix")] mod tests { + //Okay to unwrap in tests + #![allow(clippy::unwrap_in_result)] + #![allow(clippy::unwrap_used)] use super::*; #[test] diff --git a/src/steam/utils.rs b/src/steam/utils.rs index cbf4827..c3cd876 100644 --- a/src/steam/utils.rs +++ b/src/steam/utils.rs @@ -8,14 +8,14 @@ use steam_shortcuts_util::{parse_shortcuts, shortcut::ShortcutOwned}; use super::SteamSettings; -pub fn get_shortcuts_for_user(user: &SteamUsersInfo) -> ShortcutInfo { +pub fn get_shortcuts_for_user(user: &SteamUsersInfo) -> eyre::Result { let mut shortcuts = vec![]; let new_path = match &user.shortcut_path { Some(shortcut_path) => { - let content = std::fs::read(shortcut_path).unwrap(); + let content = std::fs::read(shortcut_path)?; shortcuts = parse_shortcuts(content.as_slice()) - .unwrap() + .map_err(|e| eyre::format_err!("Could not parse shortcuts: {:?}", e))? .iter() .map(|s| s.to_owned()) .collect(); @@ -27,15 +27,15 @@ pub fn get_shortcuts_for_user(user: &SteamUsersInfo) -> ShortcutInfo { user.steam_user_data_folder ); let path = Path::new(&user.steam_user_data_folder).join("config"); - std::fs::create_dir_all(path.clone()).unwrap(); + std::fs::create_dir_all(path.clone())?; path.join("shortcuts.vdf") } }; - ShortcutInfo { + Ok(ShortcutInfo { shortcuts, path: new_path, - } + }) } pub struct ShortcutInfo { @@ -51,22 +51,22 @@ pub struct SteamUsersInfo { } /// Get the paths to the steam users shortcuts (one for each user) -pub fn get_shortcuts_paths( - settings: &SteamSettings, -) -> Result, Box> { +pub fn get_shortcuts_paths(settings: &SteamSettings) -> eyre::Result> { let steam_path_str = get_steam_path(settings)?; let steam_path = Path::new(&steam_path_str); if !steam_path.exists() { - return Result::Err(Box::new(SteamFolderNotFound { - location_tried: format!("{:?}", steam_path), - })); + return Err(eyre::format_err!( + "Steam folder not found at: {:?}", + steam_path + )); } let user_data_path = steam_path.join("userdata"); if !user_data_path.exists() { - return Result::Err(Box::new(SteamFolderNotFound { - location_tried: format!("{:?}", user_data_path), - })); + return Err(eyre::format_err!( + "Steam user data folder not found at: {:?}", + user_data_path + )); } if !user_data_path.exists() {} @@ -89,7 +89,7 @@ pub fn get_shortcuts_paths( if shortcuts_path.exists() { return SteamUsersInfo { steam_user_data_folder: folder_string, - shortcut_path: Some(shortcuts_path.to_str().unwrap().to_string()), + shortcut_path: Some(shortcuts_path.to_string_lossy().to_string()), user_id, }; } else { @@ -104,7 +104,7 @@ pub fn get_shortcuts_paths( Ok(users_info) } -pub fn get_steam_path(settings: &SteamSettings) -> Result> { +pub fn get_steam_path(settings: &SteamSettings) -> eyre::Result { let user_location = settings.location.clone(); let steam_path_str = match user_location { Some(location) => location, @@ -113,7 +113,7 @@ pub fn get_steam_path(settings: &SteamSettings) -> Result Result> { +pub fn get_default_location() -> eyre::Result { #[cfg(target_os = "windows")] let path_string = { let key = "PROGRAMFILES(X86)"; @@ -198,8 +198,8 @@ impl Error for SteamUsersDataEmpty { self.location_tried.as_str() } } -pub fn get_users_images(user: &SteamUsersInfo) -> Result, Box> { - let grid_folder = Path::new(user.steam_user_data_folder.as_str()).join("config/grid"); +pub fn get_users_images(data_folder: &str) -> Result, Box> { + let grid_folder = Path::new(data_folder).join("config/grid"); if !grid_folder.exists() { std::fs::create_dir_all(&grid_folder)?; } diff --git a/src/steamgriddb/cached_search.rs b/src/steamgriddb/cached_search.rs index 7f6c2fc..4988923 100644 --- a/src/steamgriddb/cached_search.rs +++ b/src/steamgriddb/cached_search.rs @@ -19,7 +19,9 @@ impl<'a> CachedSearch<'a> { } pub fn save(&self) { - save_search_map(&self.search_map); + if let Err(err) = save_search_map(&self.search_map){ + eprintln!("Failed saving searchmap : {:?}",err); + } } pub fn set_cache(&mut self, app_id: u32, name: S, new_grid_id: usize) @@ -58,16 +60,21 @@ impl<'a> CachedSearch<'a> { fn get_search_map() -> SearchMap { let path = get_cache_file(); if path.exists() { - let string = std::fs::read_to_string(path).unwrap(); - serde_json::from_str::(&string).expect("Failed to parse cache.json") + std::fs::read_to_string(path) + .ok() + .and_then(|string| { + serde_json::from_str::(&string).ok() + }) + .unwrap_or_default() } else { SearchMap::new() } } -fn save_search_map(search_map: &SearchMap) { - let string = serde_json::to_string(search_map).unwrap(); +fn save_search_map(search_map: &SearchMap) -> eyre::Result<()> { + let string = serde_json::to_string(search_map)?; let path = get_cache_file(); - let mut file = File::create(path).unwrap(); - file.write_all(string.as_bytes()).unwrap(); + let mut file = File::create(path)?; + file.write_all(string.as_bytes())?; + Ok(()) } diff --git a/src/steamgriddb/downloader.rs b/src/steamgriddb/downloader.rs index a4f4eec..c9d88f4 100644 --- a/src/steamgriddb/downloader.rs +++ b/src/steamgriddb/downloader.rs @@ -23,10 +23,31 @@ use crate::sync::SyncProgress; const CONCURRENT_REQUESTS: usize = 10; +impl SearchSettings for Settings { + fn download_animated(&self) -> bool { + self.steamgrid_db.prefer_animated + } + + fn download_big_picture(&self) -> bool { + self.steam.optimize_for_big_picture + } + + fn allow_nsfw(&self) -> bool { + self.steamgrid_db.allow_nsfw + } + + fn only_download_boilr_images(&self) -> bool { + self.steamgrid_db.only_download_boilr_images + } + + fn is_image_banned(&self, image_type: &ImageType, app_id: u32) -> bool { + self.steamgrid_db.is_image_banned(image_type, app_id) + } +} + pub async fn download_images_for_users<'b>( settings: &Settings, users: &[SteamUsersInfo], - download_animated: bool, sender: &mut Option>, ) { let auth_key = &settings.steamgrid_db.auth_key; @@ -40,24 +61,29 @@ pub async fn download_images_for_users<'b>( if let Some(sender) = sender { let _ = sender.send(SyncProgress::FindingImages); } - let to_downloads = stream::iter(users) - .map(|user| { - let shortcut_info = get_shortcuts_for_user(user); - async move { - let known_images = get_users_images(user).unwrap_or_default(); - let res = search_for_images_to_download( - known_images, - user.steam_user_data_folder.as_str(), - &shortcut_info.shortcuts, - search, - client, - download_animated, - settings.steam.optimize_for_big_picture, - settings, - ) - .await; - res.unwrap_or_default() - } + + let users_info = users.iter().filter_map(|user| { + let shortcut_info = get_shortcuts_for_user(user); + shortcut_info + .map(|shortcut_info| { + let data_folder = &user.steam_user_data_folder; + (shortcut_info, data_folder) + }) + .ok() + }); + let to_downloads = stream::iter(users_info) + .map(|(shortcut_info, data_folder)| async move { + let known_images = get_users_images(data_folder).unwrap_or_default(); + let res = search_for_images_to_download( + known_images, + data_folder.as_str(), + &shortcut_info.shortcuts, + search, + client, + settings, + ) + .await; + res.unwrap_or_default() }) .buffer_unordered(CONCURRENT_REQUESTS) .collect::>>() @@ -128,15 +154,21 @@ pub struct PublicGameResponse { data: Option, } -async fn search_for_images_to_download( +pub trait SearchSettings { + fn download_animated(&self) -> bool; + fn download_big_picture(&self) -> bool; + fn allow_nsfw(&self) -> bool; + fn only_download_boilr_images(&self) -> bool; + fn is_image_banned(&self, image_type: &ImageType, app_id: u32) -> bool; +} + +async fn search_for_images_to_download( known_images: Vec, user_data_folder: &str, shortcuts: &[ShortcutOwned], search: &CachedSearch<'_>, client: &Client, - download_animated: bool, - download_big_picture: bool, - settings: &Settings, + search_settins: &T, ) -> Result, Box> { let types = { let mut types = vec![ @@ -146,7 +178,7 @@ async fn search_for_images_to_download( ImageType::WideGrid, ImageType::Icon, ]; - if download_big_picture { + if search_settins.download_big_picture() { types.push(ImageType::BigPicture); } types @@ -154,7 +186,7 @@ async fn search_for_images_to_download( let shortcuts_to_search_for = shortcuts .iter() - .filter(|s| !settings.steamgrid_db.only_download_boilr_images || s.is_boilr_shortcut()) + .filter(|s| !search_settins.only_download_boilr_images() || s.is_boilr_shortcut()) .filter(|s| { // if we are missing any of the images we need to search for them types @@ -171,13 +203,10 @@ async fn search_for_images_to_download( let search_results_a = stream::iter(shortcuts_to_search_for) .map(|s| async move { let search_result = search.search(s.app_id, &s.app_name).await; - if search_result.is_err() { - return None; + match search_result { + Ok(Some(search_result)) => Some((s.app_id, search_result)), + _ => None, } - let search_result = search_result.unwrap(); - search_result?; - let search_result = search_result.unwrap(); - Some((s.app_id, search_result)) }) .buffer_unordered(CONCURRENT_REQUESTS) .collect::>>() @@ -192,7 +221,7 @@ async fn search_for_images_to_download( let images_needed = shortcuts .iter() .filter(|s| search_results.contains_key(&s.app_id)) - .filter(|s| !settings.steamgrid_db.is_image_banned(&image_type, s.app_id)) + .filter(|s| !search_settins.is_image_banned(&image_type, s.app_id)) .filter(|s| !known_images.contains(&image_type.file_name_no_extension(s.app_id))); let image_ids: Vec = images_needed .clone() @@ -203,8 +232,14 @@ async fn search_for_images_to_download( let shortcuts: Vec<&ShortcutOwned> = images_needed.collect(); for image_ids in image_ids.chunks(99) { - let image_search_result = - get_images_for_ids(client, image_ids, &image_type, download_animated, settings.steamgrid_db.allow_nsfw).await; + let image_search_result = get_images_for_ids( + client, + image_ids, + &image_type, + search_settins.download_animated(), + search_settins.allow_nsfw(), + ) + .await; match image_search_result { Ok(images) => { let images = images @@ -237,7 +272,7 @@ async fn search_for_images_to_download( to_download.extend(download_for_this_type); } - Err(err) => println!("Error getting images: {}", err), + Err(err) => eprintln!("Error getting images: {}", err), } } } @@ -388,17 +423,17 @@ fn icon_url(steam_app_id: &str, icon_id: &str) -> String { ) } -pub async fn download_to_download(to_download: &ToDownload) -> Result<(), Box> { +pub async fn download_to_download(to_download: &ToDownload) -> eyre::Result<()> { println!( "Downloading {:?} for {} to {:?}", to_download.image_type, to_download.app_name, to_download.path ); let path = &to_download.path; let url = &to_download.url; - let mut file = File::create(path).unwrap(); + let mut file = File::create(path)?; let response = reqwest::get(url).await?; let content = response.bytes().await?; - file.write_all(&content).unwrap(); + file.write_all(&content)?; Ok(()) } diff --git a/src/sync/synchronization.rs b/src/sync/synchronization.rs index 172c6a1..08ea21a 100644 --- a/src/sync/synchronization.rs +++ b/src/sync/synchronization.rs @@ -34,15 +34,16 @@ pub fn disconnect_shortcut(settings: &Settings, app_id: u32) -> Result<(), Strin .map_err(|e| format!("Getting shortcut paths failed: {e}"))?; for user in userinfo_shortcuts.iter_mut() { - let mut shortcut_info = get_shortcuts_for_user(user); - - for shortcut in shortcut_info.shortcuts.iter_mut() { - if shortcut.app_id == app_id { - shortcut.dev_kit_game_id = "".to_string(); - shortcut.tags.retain(|s| s != BOILR_TAG); + let shortcut_info = get_shortcuts_for_user(user); + if let Ok(mut shortcut_info) = shortcut_info { + for shortcut in shortcut_info.shortcuts.iter_mut() { + if shortcut.app_id == app_id { + shortcut.dev_kit_game_id = "".to_string(); + shortcut.tags.retain(|s| s != BOILR_TAG); + } } + save_shortcuts(&shortcut_info.shortcuts, Path::new(&shortcut_info.path)); } - save_shortcuts(&shortcut_info.shortcuts, Path::new(&shortcut_info.path)); } Ok(()) @@ -53,9 +54,8 @@ pub fn sync_shortcuts( platform_shortcuts: &[(String, Vec)], sender: &mut Option>, renames: &HashMap, -) -> Result, String> { - let mut userinfo_shortcuts = get_shortcuts_paths(&settings.steam) - .map_err(|e| format!("Getting shortcut paths failed: {e}"))?; +) -> eyre::Result> { + let mut userinfo_shortcuts = get_shortcuts_paths(&settings.steam)?; let mut all_shortcuts: Vec = platform_shortcuts .iter() .flat_map(|s| s.1.clone()) @@ -86,10 +86,14 @@ pub fn sync_shortcuts( println!("Appid: {} name: {}", shortcut.app_id, shortcut.app_name); } println!("Found {} user(s)", userinfo_shortcuts.len()); - for user in userinfo_shortcuts.iter_mut() { + let ok_shorcuts = userinfo_shortcuts.iter_mut().filter_map(|user|{ + let shortcut_info = get_shortcuts_for_user(user).ok(); + shortcut_info.map(|shortcut_info| { + (user,shortcut_info) + }) + }); + for (user,mut shortcut_info) in ok_shorcuts { let start_time = std::time::Instant::now(); - - let mut shortcut_info = get_shortcuts_for_user(user); println!( "Found {} shortcuts for user: {}", shortcut_info.shortcuts.len(), @@ -99,7 +103,7 @@ pub fn sync_shortcuts( remove_old_shortcuts(&mut shortcut_info); remove_shortcuts_with_same_appid(&mut shortcut_info, &all_shortcuts); - shortcut_info.shortcuts.extend(all_shortcuts.clone()); + shortcut_info.shortcuts.extend(all_shortcuts.clone()); save_shortcuts(&shortcut_info.shortcuts, Path::new(&shortcut_info.path)); @@ -122,11 +126,12 @@ pub async fn download_images( 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, sender).await; + if settings.steamgrid_db.prefer_animated{ + let mut set = settings.clone(); + set.steamgrid_db.prefer_animated = false; + download_images_for_users(&set, userinfo_shortcuts, sender).await; } - download_images_for_users(settings, userinfo_shortcuts, false, sender).await; } } @@ -157,21 +162,25 @@ fn remove_old_shortcuts(shortcut_info: &mut ShortcutInfo) { .retain(|shortcut| !shortcut.is_boilr_shortcut()); } -pub fn fix_all_shortcut_icons ( - settings: &Settings, -) -> eyre::Result<()>{ - let mut userinfo_shortcuts = get_shortcuts_paths(&settings.steam).map_err(|e|eyre::format_err!("Could not find steam shortcuts; {e}"))?; +pub fn fix_all_shortcut_icons(settings: &Settings) -> eyre::Result<()> { + let mut userinfo_shortcuts = get_shortcuts_paths(&settings.steam) + .map_err(|e| eyre::format_err!("Could not find steam shortcuts; {e}"))?; for user in userinfo_shortcuts.iter_mut() { - let mut shortcut_info = get_shortcuts_for_user(user); - let changes = fix_shortcut_icons(user,&mut shortcut_info.shortcuts,settings.steam.optimize_for_big_picture); - if changes{ - save_shortcuts(&shortcut_info.shortcuts, Path::new(&shortcut_info.path)); + let shortcut_info = get_shortcuts_for_user(user); + if let Ok(mut shortcut_info) = shortcut_info { + let changes = fix_shortcut_icons( + user, + &mut shortcut_info.shortcuts, + settings.steam.optimize_for_big_picture, + ); + if changes { + save_shortcuts(&shortcut_info.shortcuts, Path::new(&shortcut_info.path)); + } } } Ok(()) } - fn fix_shortcut_icons( user: &SteamUsersInfo, shortcuts: &mut Vec, @@ -194,7 +203,7 @@ fn fix_shortcut_icons( let path = image_folder.join(image_type.file_name(app_id, ext)); if !icon_exsists && path.exists() { shortcut.icon = path.to_string_lossy().to_string(); - has_changes= true; + has_changes = true; break; } } diff --git a/src/ui/components/game_image_button.rs b/src/ui/components/game_image_button.rs index c93595c..b927306 100644 --- a/src/ui/components/game_image_button.rs +++ b/src/ui/components/game_image_button.rs @@ -1,147 +1,186 @@ -use std::path::Path; +use std::path::{Path, PathBuf}; use egui::{Button, ImageButton}; use futures::executor::block_on; use tokio::runtime::Runtime; -use crate::steamgriddb::{ImageType, ToDownload}; +use crate::steamgriddb::{ToDownload, ImageType}; use crate::ui::images::{clamp_to_width, ImageHandles, TextureDownloadState}; use crate::ui::ui_images::load_image_from_path; - -pub fn render_image_from_path( - ui: &mut egui::Ui, - image_handles: &ImageHandles, - path: &Path, +pub struct GameButton { + path: PathBuf, max_width: f32, - text: &str, -) -> bool { - render_possible_image(ui, image_handles, path, max_width, text, &ImageType::Grid, None, None) + text: String, + image_type: ImageType, } -pub fn render_image_from_path_image_type( - ui: &mut egui::Ui, - image_handles: &ImageHandles, - path: &Path, - max_width: f32, - text: &str, - image_type: &ImageType, -) -> bool { - render_possible_image(ui, image_handles, path, max_width, text, image_type, None, None) -} - - -pub fn render_image_from_path_or_url( - ui: &mut egui::Ui, - image_handles: &ImageHandles, - path: &Path, - max_width: f32, - text: &str, - image_type: &ImageType, - rt: &Runtime, - url: &str, -) -> bool { - render_possible_image( - ui, - image_handles, - path, - max_width, - text, - image_type, - Some(rt), - Some(url), - ) -} - -fn render_possible_image( - ui: &mut egui::Ui, - image_handles: &ImageHandles, - path: &Path, - max_width: f32, - text: &str, - image_type: &ImageType, - rt: Option<&Runtime>, - url: Option<&str>, -) -> bool { - let image_key = path.to_string_lossy().to_string(); - - match image_handles.get_mut(&image_key) { - Some(mut state) => { - match state.value() { - TextureDownloadState::Downloading => { - ui.ctx().request_repaint(); - //nothing to do,just wait - ui.spinner(); - } - TextureDownloadState::Downloaded => { - //Need to load - let image_data = load_image_from_path(path); - match image_data { - Ok(image_data) => { - let handle = ui.ctx().load_texture( - &image_key, - image_data, - egui::TextureOptions::LINEAR, - ); - *state.value_mut() = TextureDownloadState::Loaded(handle); - ui.spinner(); - } - Err(_) => *state.value_mut() = TextureDownloadState::Failed, - } - ui.ctx().request_repaint(); - } - TextureDownloadState::Loaded(texture_handle) => { - //need to show - let mut size = texture_handle.size_vec2(); - clamp_to_width(&mut size, max_width); - let image_button = ImageButton::new(texture_handle, size); - if ui - .add_sized(size, image_button) - .on_hover_text(text) - .clicked() - { - return true; - } - } - TextureDownloadState::Failed => { - let button = - ui.add_sized([max_width, max_width * image_type.ratio()], Button::new(text).wrap(true)); - if button.clicked() { - return true; - } - } - } - } - None => { - if !path.exists() && url.is_none() { - image_handles.insert(image_key, TextureDownloadState::Failed); - } else { - //We need to start a download - //Redownload if file is too small - if !path.exists() - || std::fs::metadata(path).map(|m| m.len()).unwrap_or_default() < 2 - { - image_handles.insert(image_key.clone(), TextureDownloadState::Downloading); - let to_download = ToDownload { - path: path.to_path_buf(), - url: url.unwrap().to_string(), - app_name: "Thumbnail".to_string(), - image_type: *image_type, - }; - let image_handles = image_handles.clone(); - let image_key = image_key.clone(); - if let Some(rt) = rt { - rt.spawn_blocking(move || { - block_on(crate::steamgriddb::download_to_download(&to_download)) - .unwrap(); - image_handles.insert(image_key, TextureDownloadState::Downloaded); - }); - } - } else { - image_handles.insert(image_key.clone(), TextureDownloadState::Downloaded); - } - } +impl GameButton { + pub fn new(path: &Path) -> Self { + Self { + max_width: 200.0, + path: path.to_path_buf(), + text: Default::default(), + image_type: ImageType::Grid, + } + } + pub fn width(&mut self, max_width: f32) -> &mut Self { + self.max_width = max_width; + self + } + + pub fn text(&mut self, text: &str) -> &mut Self { + self.text = text.to_string(); + self + } + + pub fn image_type(&mut self, image_type:&ImageType) -> &mut Self{ + self.image_type = *image_type; + self + } + + pub fn show_download( + &self, + ui: &mut egui::Ui, + image_handles: &ImageHandles, + rt: &Runtime, + url: &str, + ) -> bool { + self.render_possible_image(ui, image_handles, Some(rt), Some(url)) + } + + pub fn show(&self, ui: &mut egui::Ui, image_handles: &ImageHandles) -> bool { + self.render_possible_image(ui, image_handles, None, None) + } + + fn render_possible_image( + &self, + ui: &mut egui::Ui, + image_handles: &ImageHandles, + rt: Option<&Runtime>, + url: Option<&str>, + ) -> bool { + { + let path = &self.path; + let image_key = path.to_string_lossy().to_string(); + match image_handles.get_mut(&image_key) { + Some(mut state) => { + match state.value() { + TextureDownloadState::Downloading => { + ui.ctx().request_repaint(); + //nothing to do,just wait + ui.spinner(); + } + TextureDownloadState::Downloaded => { + //Need to load + let image_data = load_image_from_path(path); + match image_data { + Ok(image_data) => { + let handle = ui.ctx().load_texture( + &image_key, + image_data, + egui::TextureOptions::LINEAR, + ); + *state.value_mut() = TextureDownloadState::Loaded(handle); + ui.spinner(); + } + Err(_) => *state.value_mut() = TextureDownloadState::Failed, + } + ui.ctx().request_repaint(); + } + TextureDownloadState::Loaded(texture_handle) => { + //need to show + let mut size = texture_handle.size_vec2(); + clamp_to_width(&mut size, self.max_width); + let image_button = ImageButton::new(texture_handle, size); + if ui + .add_sized(size, image_button) + .on_hover_text(&self.text) + .clicked() + { + return true; + } + } + TextureDownloadState::Failed => { + let button = ui.add_sized( + [self.max_width, self.max_width * self.image_type.ratio()], + Button::new(&self.text).wrap(true), + ); + if button.clicked() { + return true; + } + } + } + } + None => { + match url { + Some(url) => { + download_image( + path, + image_handles, + &image_key, + url, + &self.image_type, + rt, + ); + } + None => { + //Not possible to download + if !path.exists() { + image_handles.insert(image_key, TextureDownloadState::Failed); + } + } + } + } + } + false } } - false +} +impl Default for GameButton { + fn default() -> Self { + Self::new(Path::new("")) + } +} + + +fn download_image( + path: &Path, + image_handles: &ImageHandles, + image_key: &str, + url: &str, + image_type: &ImageType, + rt: Option<&Runtime>, +) { + //We need to start a download + //Redownload if file is too small + if !path.exists() || std::fs::metadata(path).map(|m| m.len()).unwrap_or_default() < 2 { + image_handles.insert(image_key.to_string(), TextureDownloadState::Downloading); + let to_download = ToDownload { + path: path.to_path_buf(), + url: url.to_string(), + app_name: "Thumbnail".to_string(), + image_type: *image_type, + }; + let image_handles = image_handles.clone(); + let image_key = image_key.to_string(); + if let Some(rt) = rt { + rt.spawn_blocking(move || { + match block_on(crate::steamgriddb::download_to_download(&to_download)) { + Ok(_) => { + image_handles.insert(image_key, TextureDownloadState::Downloaded); + } + Err(err) => { + println!( + "Failed downloading image {} error: {:?}", + to_download.url, err + ); + image_handles.insert(image_key, TextureDownloadState::Failed); + } + } + }); + } + } else { + image_handles.insert(image_key.to_string(), TextureDownloadState::Downloaded); + } } diff --git a/src/ui/components/mod.rs b/src/ui/components/mod.rs index 5983c26..3265c39 100644 --- a/src/ui/components/mod.rs +++ b/src/ui/components/mod.rs @@ -2,6 +2,4 @@ mod steam_user_select; mod game_image_button; pub use steam_user_select::render_user_select; -pub use game_image_button::render_image_from_path; -pub use game_image_button::render_image_from_path_or_url; -pub use game_image_button::render_image_from_path_image_type; \ No newline at end of file +pub use game_image_button::GameButton; \ No newline at end of file diff --git a/src/ui/defines.rs b/src/ui/defines.rs index 92e4499..a4c96d6 100644 --- a/src/ui/defines.rs +++ b/src/ui/defines.rs @@ -23,19 +23,19 @@ pub mod ui_images { pub const LOGO_ICON: &[u8] = include_bytes!("../../resources/logo_small.png"); pub fn get_import_image() -> ImageData { - ImageData::Color(load_image_from_memory(IMPORT_GAMES_IMAGE).unwrap()) + ImageData::Color(load_image_from_memory(IMPORT_GAMES_IMAGE).unwrap_or_default()) } pub fn get_save_image() -> ImageData { - ImageData::Color(load_image_from_memory(SAVE_IMAGE).unwrap()) + ImageData::Color(load_image_from_memory(SAVE_IMAGE).unwrap_or_default()) } pub fn get_logo() -> ImageData { - ImageData::Color(load_image_from_memory(LOGO_32).unwrap()) + ImageData::Color(load_image_from_memory(LOGO_32).unwrap_or_default()) } pub fn get_logo_icon() -> IconData { - let image = image::load_from_memory(LOGO_ICON).unwrap(); + let image = image::load_from_memory(LOGO_ICON).unwrap_or_default(); let image_buffer = image.to_rgba8(); let pixels = image_buffer.as_flat_samples(); IconData { diff --git a/src/ui/images/gametype.rs b/src/ui/images/gametype.rs index 482ee2b..3852602 100644 --- a/src/ui/images/gametype.rs +++ b/src/ui/images/gametype.rs @@ -6,7 +6,7 @@ use crate::{steam::SteamGameInfo}; #[derive(Debug, Clone)] pub enum GameType { - Shortcut(ShortcutOwned), + Shortcut(Box), SteamGame(SteamGameInfo), } diff --git a/src/ui/images/hasimagekey.rs b/src/ui/images/hasimagekey.rs index 41012fa..bc0a743 100644 --- a/src/ui/images/hasimagekey.rs +++ b/src/ui/images/hasimagekey.rs @@ -2,19 +2,15 @@ use std::path::{Path, PathBuf}; use steam_shortcuts_util::shortcut::ShortcutOwned; -use crate::{steamgriddb::ImageType, steam::SteamGameInfo}; - -use super::{gametype::GameType, constants::POSSIBLE_EXTENSIONS}; +use crate::{steam::SteamGameInfo, steamgriddb::ImageType}; +use super::{constants::POSSIBLE_EXTENSIONS, gametype::GameType}; pub trait HasImageKey { - ///Gives a unique key to an image given its type and user path fn key(&self, image_type: &ImageType, user_path: &Path) -> (PathBuf, String); - } - impl HasImageKey for GameType { fn key(&self, image_type: &ImageType, user_path: &Path) -> (PathBuf, String) { match self { @@ -24,31 +20,29 @@ impl HasImageKey for GameType { } } - impl HasImageKey for SteamGameInfo { fn key(&self, image_type: &ImageType, user_path: &Path) -> (PathBuf, String) { - let mut keys = POSSIBLE_EXTENSIONS - .iter() - .map(|ext| key_from_extension(self.appid, image_type, user_path, ext)); - let first = keys.next().unwrap(); - let other = keys.find(|(exsists, _, _)| *exsists); - let (_, path, key) = other.unwrap_or(first); - (path, key) + let app_id = self.appid; + key(app_id, image_type, user_path) } } impl HasImageKey for ShortcutOwned { fn key(&self, image_type: &ImageType, user_path: &Path) -> (PathBuf, String) { - let mut keys = POSSIBLE_EXTENSIONS - .iter() - .map(|ext| key_from_extension(self.app_id, image_type, user_path, ext)); - let first = keys.next().unwrap(); - let other = keys.find(|(exsists, _, _)| *exsists); - let (_, path, key) = other.unwrap_or(first); - (path, key) + let app_id = self.app_id; + key(app_id, image_type, user_path) } } +fn key(app_id: u32, image_type: &ImageType, user_path: &Path) -> (PathBuf, String) { + let ext = |ext| key_from_extension(app_id, image_type, user_path, ext); + let keys = POSSIBLE_EXTENSIONS.map(ext); + let other = keys.iter().find(|(exsists, _, _)| *exsists); + let first = ext(POSSIBLE_EXTENSIONS[0]); + let (_, path, key) = other.unwrap_or(&first); + (path.to_path_buf(), key.to_string()) +} + fn key_from_extension( app_id: u32, image_type: &ImageType, diff --git a/src/ui/images/pages/pick_new_image.rs b/src/ui/images/pages/pick_new_image.rs index 81b2dc3..7e683be 100644 --- a/src/ui/images/pages/pick_new_image.rs +++ b/src/ui/images/pages/pick_new_image.rs @@ -1,20 +1,18 @@ -use std::path::Path; +use std::path::{Path, PathBuf}; -use egui::{Grid, }; +use egui::Grid; use futures::executor::block_on; -use tokio::{ sync::watch}; +use tokio::sync::watch; use crate::{ steamgriddb::{get_image_extension, ImageType, ToDownload}, ui::{ + components::GameButton, images::{ - constants::MAX_WIDTH, - hasimagekey::HasImageKey, - image_select_state::{ ImageSelectState}, - possible_image::PossibleImage, - useraction::UserAction, + constants::MAX_WIDTH, hasimagekey::HasImageKey, image_select_state::ImageSelectState, + possible_image::PossibleImage, useraction::UserAction, }, - FetcStatus, MyEguiApp, components::render_image_from_path_or_url, + FetcStatus, MyEguiApp, }, }; @@ -61,16 +59,10 @@ pub fn render_page_pick_image( .show(ui, |ui| { for image in images { let path = image.thumbnail_path.as_path(); - if render_image_from_path_or_url( - ui, - &state.image_handles, - path, - column_width, - &image.full_url, - image_type, - &app.rt, - &image.thumbnail_url, - ) { + let mut button = GameButton::new(path); + button.width(column_width); + button.text("Pick image"); + if button.show_download(ui, &state.image_handles, &app.rt,&image.thumbnail_url) { return Some(image.clone()); } column += 1; @@ -82,9 +74,9 @@ pub fn render_page_pick_image( None }) .inner; - if let Some(x) = x { + if let Some(x) = x { return Some(UserAction::ImageSelected(x)); - } + } } _ => { ui.horizontal(|ui| { @@ -99,76 +91,78 @@ pub fn render_page_pick_image( pub fn handle_image_selected(app: &mut MyEguiApp, image: PossibleImage) { //We must have a user here - let user = app.image_selected_state.steam_user.as_ref().unwrap(); - let selected_image_type = app - .image_selected_state - .image_type_selected - .as_ref() - .unwrap(); - let selected_shortcut = app.image_selected_state.selected_shortcut.as_ref().unwrap(); + let state = &app.image_selected_state; + if let (Some(user), Some(selected_image_type), Some(selected_shortcut)) = ( + state.steam_user.as_ref(), + state.image_type_selected.as_ref(), + state.selected_shortcut.as_ref(), + ) { + let get_image_extension = &get_image_extension(&image.mime); + let ext = get_image_extension; + let to_download_to_path = Path::new(&user.steam_user_data_folder) + .join("config") + .join("grid") + .join(selected_image_type.file_name(selected_shortcut.app_id(), ext)); - let ext = get_image_extension(&image.mime); - let to_download_to_path = Path::new(&user.steam_user_data_folder) - .join("config") - .join("grid") - .join(selected_image_type.file_name(selected_shortcut.app_id(), ext)); + delete_images_of_type(user, selected_shortcut, selected_image_type); - //Delete old possible images - - let data_folder = Path::new(&user.steam_user_data_folder); - - //Keep deleting images of this type untill we don't find any more - let mut path = get_shortcut_image_path(app, data_folder); - while Path::new(&path).exists() { - let _ = std::fs::remove_file(&path); - path = get_shortcut_image_path(app, data_folder); - } - - //Put the loaded thumbnail into the image handler map, we can use that for preview - let full_image_key = to_download_to_path.to_string_lossy().to_string(); - let _ = app - .image_selected_state - .image_handles - .remove(&full_image_key); - let thumbnail_key = image.thumbnail_path.to_string_lossy().to_string(); - let thumbnail = app - .image_selected_state - .image_handles - .remove(&thumbnail_key); - if let Some((_key, thumbnail)) = thumbnail { - app.image_selected_state + //Put the loaded thumbnail into the image handler map, we can use that for preview + let full_image_key = to_download_to_path.to_string_lossy().to_string(); + let _ = app + .image_selected_state .image_handles - .insert(full_image_key, thumbnail); - } + .remove(&full_image_key); + let thumbnail_key = image.thumbnail_path.to_string_lossy().to_string(); + let thumbnail = app + .image_selected_state + .image_handles + .remove(&thumbnail_key); + if let Some((_key, thumbnail)) = thumbnail { + app.image_selected_state + .image_handles + .insert(full_image_key, thumbnail); + } - let app_name = selected_shortcut.name(); - let to_download = ToDownload { - path: to_download_to_path, - url: image.full_url.clone(), - app_name: app_name.to_string(), - image_type: *selected_image_type, - }; - app.rt.spawn_blocking(move || { - let _ = block_on(crate::steamgriddb::download_to_download(&to_download)); - }); + let app_name = selected_shortcut.name(); + let to_download = ToDownload { + path: to_download_to_path, + url: image.full_url.clone(), + app_name: app_name.to_string(), + image_type: *selected_image_type, + }; + app.rt.spawn_blocking(move || { + let _ = block_on(crate::steamgriddb::download_to_download(&to_download)); + }); - clear_loaded_images(app); - { - app.image_selected_state.image_type_selected = None; - app.image_selected_state.image_options = watch::channel(FetcStatus::NeedsFetched).1; + clear_loaded_images(app); + { + app.image_selected_state.image_type_selected = None; + app.image_selected_state.image_options = watch::channel(FetcStatus::NeedsFetched).1; + } } } -fn get_shortcut_image_path(app: &MyEguiApp, data_folder: &Path) -> String { - app.image_selected_state - .selected_shortcut - .as_ref() - .unwrap() - .key( - &app.image_selected_state.image_type_selected.unwrap(), - data_folder, - ) - .1 +fn delete_images_of_type( + user: &crate::steam::SteamUsersInfo, + selected_shortcut: &crate::ui::images::gametype::GameType, + selected_image_type: &ImageType, +) { + //Delete old possible images + let data_folder = Path::new(&user.steam_user_data_folder); + //Keep deleting images of this type untill we don't find any more + let mut path = image_path(selected_shortcut, selected_image_type, data_folder); + while path.exists() { + let _ = std::fs::remove_file(path); + path = image_path(selected_shortcut, selected_image_type, data_folder); + } +} + +fn image_path( + selected_shortcut: &crate::ui::images::gametype::GameType, + selected_image_type: &ImageType, + data_folder: &Path, +) -> PathBuf { + selected_shortcut.key(selected_image_type, data_folder).0 } fn clear_loaded_images(app: &mut MyEguiApp) { diff --git a/src/ui/images/pages/select_image_type.rs b/src/ui/images/pages/select_image_type.rs index b43f64d..e629071 100644 --- a/src/ui/images/pages/select_image_type.rs +++ b/src/ui/images/pages/select_image_type.rs @@ -1,10 +1,11 @@ use std::path::Path; +use crate::ui::components::GameButton; use crate::ui::images::{ gametype::GameType, hasimagekey::HasImageKey, image_select_state::ImageSelectState, useraction::UserAction, ImageHandles, }; -use crate::{steamgriddb::ImageType, ui::components::render_image_from_path_image_type}; +use crate::{steamgriddb::ImageType}; const MAX_WIDTH: f32 = 300.; @@ -12,59 +13,65 @@ pub fn render_page_shortcut_select_image_type( ui: &mut egui::Ui, state: &ImageSelectState, ) -> Option { - let shortcut = state.selected_shortcut.as_ref().unwrap(); - let user_path = &state.steam_user.as_ref().unwrap().steam_user_data_folder; - - let thumbnail = |ui: &mut egui::Ui, image_type: &ImageType| { - if render_thumbnail(ui, &state.image_handles, shortcut, image_type, user_path) { - Some(UserAction::ImageTypeSelected(*image_type)) - } else { - None - } - }; - let x = if ui.available_width() > MAX_WIDTH * 3. { - ui.horizontal(|ui| { - let x = ui.vertical(|ui| thumbnail(ui, &ImageType::Grid)).inner; - if x.is_some() { - return x; + let shortcut = &state.selected_shortcut.as_ref(); + let user_path = state + .steam_user + .as_ref() + .map(|user| &user.steam_user_data_folder); + if let (Some(shortcut), Some(user_path)) = (shortcut, user_path) { + let thumbnail = |ui: &mut egui::Ui, image_type: &ImageType| { + if render_thumbnail(ui, &state.image_handles, shortcut, image_type, user_path) { + Some(UserAction::ImageTypeSelected(*image_type)) + } else { + None } - let x = ui - .vertical(|ui| { - let types = &[ImageType::Hero, ImageType::WideGrid, ImageType::Logo]; + }; + let x = if ui.available_width() > MAX_WIDTH * 3. { + ui.horizontal(|ui| { + let x = ui.vertical(|ui| thumbnail(ui, &ImageType::Grid)).inner; + if x.is_some() { + return x; + } + let x = ui + .vertical(|ui| { + let types = &[ImageType::Hero, ImageType::WideGrid, ImageType::Logo]; + types + .iter() + .flat_map(|image_type| thumbnail(ui, image_type)) + .next() + }) + .inner; + if x.is_some() { + return x; + } + ui.vertical(|ui| { + let types = &[ImageType::Icon, ImageType::BigPicture]; types .iter() .flat_map(|image_type| thumbnail(ui, image_type)) .next() }) - .inner; - if x.is_some() { - return x; - } - ui.vertical(|ui| { - let types = &[ImageType::Icon, ImageType::BigPicture]; - types - .iter() - .flat_map(|image_type| thumbnail(ui, image_type)) - .next() + .inner }) .inner - }) - .inner - } else { - let types = ImageType::all(); - types - .iter() - .flat_map(|image_type| thumbnail(ui, image_type)) - .next() - }; + } else { + let types = ImageType::all(); + types + .iter() + .flat_map(|image_type| thumbnail(ui, image_type)) + .next() + }; - if ui - .button("Click here if the images are for a wrong game") - .clicked() - { - return Some(UserAction::CorrectGridId); + if ui + .button("Click here if the images are for a wrong game") + .clicked() + { + return Some(UserAction::CorrectGridId); + } + x + } else { + None } - x } fn render_thumbnail( @@ -76,12 +83,10 @@ fn render_thumbnail( ) -> bool { let (path, _key) = shortcut.key(image_type, Path::new(&user_path)); let text = format!("Pick {} image", image_type.name()); - render_image_from_path_image_type( - ui, - image_handles, - path.as_path(), - MAX_WIDTH, - &text, - image_type, - ) + let mut image = GameButton::new(&path); + image + .width(MAX_WIDTH) + .text(&text) + .image_type(image_type) + .show(ui, image_handles) } diff --git a/src/ui/images/pages/shortcut_images_overview.rs b/src/ui/images/pages/shortcut_images_overview.rs index 97c0cb1..db27a97 100644 --- a/src/ui/images/pages/shortcut_images_overview.rs +++ b/src/ui/images/pages/shortcut_images_overview.rs @@ -4,27 +4,30 @@ use steam_shortcuts_util::shortcut::ShortcutOwned; use crate::{ steam::SteamUsersInfo, - steamgriddb::{ImageType, CachedSearch}, + steamgriddb::{CachedSearch, ImageType}, ui::{ images::{ - gametype::GameType, hasimagekey::HasImageKey, - texturestate::TextureDownloadState, useraction::UserAction, + gametype::GameType, hasimagekey::HasImageKey, texturestate::TextureDownloadState, + useraction::UserAction, }, - MyEguiApp, ui_images::load_image_from_path, components::render_image_from_path, + ui_images::load_image_from_path, + MyEguiApp, components::GameButton, }, }; -pub fn render_page_shortcut_images_overview(app: &MyEguiApp, ui: &mut egui::Ui) -> Option { +pub fn render_page_shortcut_images_overview( + app: &MyEguiApp, + ui: &mut egui::Ui, +) -> Option { + let user_info = &app.image_selected_state.steam_user; let shortcuts = &app.image_selected_state.user_shortcuts; - let width = ui.available_size().x; let column_width = 100.; let column_padding = 23.; let columns = (width / (column_width + column_padding)).floor() as u32; let mut cur_column = 0; - match shortcuts { - Some(shortcuts) => { - let user_info = &app.image_selected_state.steam_user.as_ref().unwrap(); + match (user_info, shortcuts) { + (Some(user_info), Some(shortcuts)) => { if let Some(action) = egui::Grid::new("ui_images") .show(ui, |ui| { for shortcut in shortcuts { @@ -46,7 +49,7 @@ pub fn render_page_shortcut_images_overview(app: &MyEguiApp, ui: &mut egui::Ui) return action; } } - None => { + _ => { ui.label("Could not find any shortcuts"); } } @@ -56,7 +59,7 @@ pub fn render_page_shortcut_images_overview(app: &MyEguiApp, ui: &mut egui::Ui) fn render_image( app: &MyEguiApp, shortcut: &ShortcutOwned, - user_info: &&SteamUsersInfo, + user_info: &SteamUsersInfo, column_width: f32, ui: &mut egui::Ui, ) -> Option> { @@ -65,18 +68,21 @@ fn render_image( Path::new(&user_info.steam_user_data_folder), ); - let clicked= render_image_from_path(ui, &app.image_selected_state.image_handles, Path::new(&key), column_width, &shortcut.app_name); + let mut button = GameButton::new(Path::new(&key)); + button.text(&shortcut.app_name); + button.width(column_width); + let clicked = button.show(ui, &app.image_selected_state.image_handles); if clicked { return Some(Some(UserAction::ShortcutSelected(GameType::Shortcut( - shortcut.clone(), + Box::new(shortcut.clone()), )))); } None } pub fn handle_shortcut_selected(app: &mut MyEguiApp, shortcut: GameType, ui: &mut egui::Ui) { - let state = &mut app.image_selected_state; - //We must have a user to make see this action; - let user = state.steam_user.as_ref().unwrap(); + let state = &mut app.image_selected_state; + //We must have a user to make see this action; + if let Some(user) = state.steam_user.as_ref() { if let Some(auth_key) = &app.settings.steamgrid_db.auth_key { let client = steamgriddb_api::Client::new(auth_key); let search = CachedSearch::new(&client); @@ -102,3 +108,4 @@ pub fn handle_shortcut_selected(app: &mut MyEguiApp, shortcut: GameType, ui: &mu } state.selected_shortcut = Some(shortcut); } +} diff --git a/src/ui/images/ui_image_download.rs b/src/ui/images/ui_image_download.rs index 1edadd7..4e2b84a 100644 --- a/src/ui/images/ui_image_download.rs +++ b/src/ui/images/ui_image_download.rs @@ -4,9 +4,9 @@ use super::{ hasimagekey::HasImageKey, image_select_state::ImageSelectState, pages::{ - handle_correct_grid_request, handle_grid_change, handle_shortcut_selected, - render_page_pick_image, render_page_shortcut_images_overview, - render_page_shortcut_select_image_type, render_page_steam_images_overview, handle_image_selected, + handle_correct_grid_request, handle_grid_change, handle_image_selected, + handle_shortcut_selected, render_page_pick_image, render_page_shortcut_images_overview, + render_page_shortcut_select_image_type, render_page_steam_images_overview, }, possible_image::PossibleImage, texturestate::TextureDownloadState, @@ -157,7 +157,7 @@ impl MyEguiApp { self.handle_image_type_selected(image_type); } UserAction::ImageSelected(image) => { - handle_image_selected(self,image); + handle_image_selected(self, image); } UserAction::BackButton => { self.handle_back_button_action(); @@ -197,11 +197,13 @@ impl MyEguiApp { .image_selected_state .selected_shortcut .as_ref() - .unwrap() - .app_id(); - self.settings - .steamgrid_db - .set_image_banned(&image_type, app_id, should_ban); + .map(|m| m.app_id()); + if let Some(app_id) = app_id { + self.settings + .steamgrid_db + .set_image_banned(&image_type, app_id, should_ban); + } + self.handle_image_type_clear(image_type); } @@ -222,38 +224,39 @@ impl MyEguiApp { self.rt.spawn_blocking(move || { let task = download_images(&settings, &users, &mut sender_op); block_on(task); - let _ = sender_op.unwrap().send(SyncProgress::Done); + if let Some(sender_op) = sender_op { + let _ = sender_op.send(SyncProgress::Done); + } }); } } fn handle_image_type_clear(&mut self, image_type: ImageType) { + let app_id = self + .image_selected_state + .selected_shortcut + .as_ref() + .map(|s| s.app_id()); let data_folder = &self .image_selected_state .steam_user .as_ref() - .unwrap() - .steam_user_data_folder; - for ext in POSSIBLE_EXTENSIONS { - let file_name = image_type.file_name( - self.image_selected_state - .selected_shortcut - .as_ref() - .unwrap() - .app_id(), - ext, - ); - let path = Path::new(data_folder) - .join("config") - .join("grid") - .join(file_name); - if path.exists() { - let _ = std::fs::remove_file(&path); + .map(|s| &s.steam_user_data_folder); + if let (Some(app_id), Some(data_folder)) = (app_id, data_folder) { + for ext in POSSIBLE_EXTENSIONS { + let file_name = image_type.file_name(app_id, ext); + let path = Path::new(data_folder) + .join("config") + .join("grid") + .join(file_name); + if path.exists() { + let _ = std::fs::remove_file(&path); + } + let key = path.to_string_lossy().to_string(); + self.image_selected_state.image_handles.remove(&key); } - let key = path.to_string_lossy().to_string(); - self.image_selected_state.image_handles.remove(&key); + self.image_selected_state.image_type_selected = None; } - self.image_selected_state.image_type_selected = None; } fn handle_set_game_mode(&mut self, game_mode: GameMode) { @@ -304,11 +307,6 @@ impl MyEguiApp { }; } - - - - - fn handle_back_button_action(&mut self) { let state = &mut self.image_selected_state; if state.possible_names.is_some() { @@ -331,28 +329,35 @@ fn load_image_grids( ui: &mut egui::Ui, ) -> Vec { let user_info = crate::steam::get_shortcuts_for_user(user); - let mut user_folder = user_info.path.clone(); - user_folder.pop(); - user_folder.pop(); - let mut shortcuts = user_info.shortcuts; - shortcuts.sort_by_key(|s| s.app_name.clone()); - let image_type = &ImageType::Grid; - for shortcut in &shortcuts { - let (path, key) = shortcut.key(image_type, &user_folder); - let loaded = state.image_handles.contains_key(&key); - if !loaded && path.exists() { - let image = load_image_from_path(&path); - if let Ok(image) = image { - let texture = ui - .ctx() - .load_texture(&key, image, egui::TextureOptions::LINEAR); - state - .image_handles - .insert(key, TextureDownloadState::Loaded(texture)); + match user_info { + Ok(user_info) => { + let mut user_folder = user_info.path.clone(); + user_folder.pop(); + user_folder.pop(); + let mut shortcuts = user_info.shortcuts; + shortcuts.sort_by_key(|s| s.app_name.clone()); + let image_type = &ImageType::Grid; + for shortcut in &shortcuts { + let (path, key) = shortcut.key(image_type, &user_folder); + let loaded = state.image_handles.contains_key(&key); + if !loaded && path.exists() { + let image = load_image_from_path(&path); + if let Ok(image) = image { + let texture = + ui.ctx() + .load_texture(&key, image, egui::TextureOptions::LINEAR); + state + .image_handles + .insert(key, TextureDownloadState::Loaded(texture)); + } + } } + shortcuts + } + Err(_err) => { + vec![] } } - shortcuts } fn render_shortcut_mode_select(state: &ImageSelectState, ui: &mut egui::Ui) -> Option { diff --git a/src/ui/ui_backup.rs b/src/ui/ui_backup.rs index 3a93ae6..2caa30d 100644 --- a/src/ui/ui_backup.rs +++ b/src/ui/ui_backup.rs @@ -62,14 +62,23 @@ impl MyEguiApp { } pub fn restore_backup(steam_settings: &SteamSettings, shortcut_path: &Path) -> bool { - let file_name = shortcut_path.file_name().unwrap(); + let file_name = shortcut_path.file_name(); let paths = get_shortcuts_paths(steam_settings); - if let Ok(paths) = paths { + if let (Ok(paths), Some(file_name)) = (paths, file_name) { for user in paths { if let Some(user_shortcut_path) = user.shortcut_path { if file_name.to_string_lossy().starts_with(&user.user_id) { - std::fs::copy(shortcut_path, Path::new(&user_shortcut_path)).unwrap(); - println!("Restored shortcut to path : {}", user_shortcut_path); + match std::fs::copy(shortcut_path, Path::new(&user_shortcut_path)) { + Ok(_) => { + println!("Restored shortcut to path : {}", user_shortcut_path); + } + Err(err) => { + eprintln!( + "Failed to restored shortcut to path : {} gave error: {:?}", + user_shortcut_path, err + ); + } + } return true; } } @@ -100,7 +109,7 @@ pub fn load_backups() -> Vec { result } -const DATE_FORMAT :&str = "[year]-[month]-[day]-[hour]-[minute]-[second]"; +const DATE_FORMAT: &str = "[year]-[month]-[day]-[hour]-[minute]-[second]"; pub fn backup_shortcuts(steam_settings: &SteamSettings) { use time::OffsetDateTime; @@ -108,18 +117,28 @@ pub fn backup_shortcuts(steam_settings: &SteamSettings) { let backup_folder = get_backups_flder(); let paths = get_shortcuts_paths(steam_settings); let date = OffsetDateTime::now_utc(); - let format = format_description::parse(DATE_FORMAT).unwrap(); - let date_string = date.format(&format).unwrap(); - if let Ok(user_infos) = paths { + let format = format_description::parse(DATE_FORMAT); + if let Ok(format) = format{ + let date_string = date.format(&format); + if let (Ok(date_string),Ok(user_infos)) = (date_string,paths) { for user_info in user_infos { if let Some(shortcut_path) = user_info.shortcut_path { let new_path = backup_folder.join(format!( "{}-{}-shortcuts.vdf", user_info.user_id, date_string )); - println!("Backed up shortcut at: {:?}", new_path); - std::fs::copy(shortcut_path, &new_path).unwrap(); + match std::fs::copy(shortcut_path, &new_path) { + Ok(_) => { + println!("Backed up shortcut at: {:?}", new_path); + } + Err(err) => { + eprintln!( + "Failed to backup shortcut at: {:?}, error: {:?}", + new_path, err + ); + } + } } - } + }} } } diff --git a/src/ui/ui_disconnect.rs b/src/ui/ui_disconnect.rs index 9237a23..6c4aa25 100644 --- a/src/ui/ui_disconnect.rs +++ b/src/ui/ui_disconnect.rs @@ -26,7 +26,9 @@ impl MyEguiApp { let mut user_info = vec![]; for user in users { let shortcut_info = get_shortcuts_for_user(&user); - user_info.push(shortcut_info); + if let Ok(shortcut_info) = shortcut_info { + user_info.push(shortcut_info); + } } user_info }) diff --git a/src/ui/ui_import_games.rs b/src/ui/ui_import_games.rs index 75031e1..914e8ff 100644 --- a/src/ui/ui_import_games.rs +++ b/src/ui/ui_import_games.rs @@ -4,6 +4,7 @@ use futures::executor::block_on; use steam_shortcuts_util::shortcut::ShortcutOwned; use tokio::sync::watch; +use tokio::task::JoinHandle; use crate::config::get_renames_file; use crate::platforms::ShortcutToImport; @@ -123,7 +124,14 @@ impl MyEguiApp { }); } - pub fn run_sync(&mut self, wait: bool) { + pub fn run_sync_blocking(&mut self) -> eyre::Result<()> { + self.run_sync(true) + } + + pub fn run_sync_async(&mut self) { + let _ = self.run_sync(false); + } + fn run_sync(&mut self, wait: bool) -> eyre::Result<()> { let (sender, reciever) = watch::channel(SyncProgress::NotStarted); let settings = self.settings.clone(); if settings.steam.stop_steam { @@ -136,7 +144,7 @@ impl MyEguiApp { let _ = sender.send(SyncProgress::Starting); if all_ready { let shortcuts_to_import = get_all_games(&self.games_to_sync); - let handle = self.rt.spawn_blocking(move || { + let handle: JoinHandle> = self.rt.spawn_blocking(move || { #[cfg(target_family = "unix")] setup_proton(shortcuts_to_import.iter()); @@ -145,12 +153,11 @@ impl MyEguiApp { let mut some_sender = Some(sender); backup_shortcuts(&settings.steam); let usersinfo = - sync::sync_shortcuts(&settings, &import_games, &mut some_sender, &renames) - .unwrap(); + sync::sync_shortcuts(&settings, &import_games, &mut some_sender, &renames)?; let task = download_images(&settings, &usersinfo, &mut some_sender); block_on(task); //Run a second time to fix up shortcuts after images are downloaded - if let Err(e) = sync::fix_all_shortcut_icons(&settings){ + if let Err(e) = sync::fix_all_shortcut_icons(&settings) { eprintln!("Could not fix shortcuts with error {e}"); } @@ -160,11 +167,13 @@ impl MyEguiApp { if settings.steam.start_steam { crate::steam::ensure_steam_started(&settings.steam); } + Ok(()) }); if wait { - self.rt.block_on(handle).unwrap(); + self.rt.block_on(handle)??; } } + Ok(()) } } @@ -202,6 +211,8 @@ where crate::sync::symlinks::create_sym_links(&shortcut_info.shortcut); } } - setup_proton_games(&shortcuts_to_proton); + if let Err(err) = setup_proton_games(&shortcuts_to_proton){ + eprintln!("failed to save proton settings: {:?}",err); + } } } diff --git a/src/ui/uiapp.rs b/src/ui/uiapp.rs index 52e96e5..5f5c44c 100644 --- a/src/ui/uiapp.rs +++ b/src/ui/uiapp.rs @@ -15,13 +15,14 @@ use crate::{ }; use super::{ + images::ImageSelectState, 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, get_save_image}, ui_import_games::FetcStatus, - BackupState, DiconnectState, images::ImageSelectState, + BackupState, DiconnectState, }; const SECTION_SPACING: f32 = 25.0; @@ -70,12 +71,12 @@ pub struct MyEguiApp { } impl MyEguiApp { - pub fn new() -> Self { - let mut runtime = Runtime::new().unwrap(); - let settings = Settings::new().expect("We must be able to load our settings"); + pub fn new() -> eyre::Result { + let mut runtime = Runtime::new()?; + let settings = Settings::new()?; let platforms = get_platforms(); let games_to_sync = create_games_to_sync(&mut runtime, &platforms); - Self { + Ok(Self { selected_menu: Menues::Import, settings, rt: runtime, @@ -88,7 +89,7 @@ impl MyEguiApp { rename_map: get_rename_map(), current_edit: Option::None, platforms, - } + }) } fn render_import_button(&mut self, ui: &mut egui::Ui) { @@ -121,17 +122,19 @@ impl MyEguiApp { let texture = self.get_import_image(ui); let size = texture.size_vec2(); let image_button = ImageButton::new(texture, size * 0.40); - if all_ready && !syncing{ + if all_ready && !syncing { if ui .add(image_button) .on_hover_text("Import your games into steam") - .clicked(){ -save_settings(&self.settings, &self.platforms); - self.run_sync(false); + .clicked() + { + if let Err(err) = save_settings(&self.settings, &self.platforms){ + eprintln!("Failed to save settings {:?}",err); } - }else{ - ui - .add(image_button) + self.run_sync_async(); + } + } else { + ui.add(image_button) .on_hover_text("Waiting for sync to finish"); } } @@ -265,7 +268,9 @@ impl App for MyEguiApp { let save_button = ImageButton::new(texture, size * 0.5); if ui.add(save_button).on_hover_text("Save settings").clicked() { - save_settings(&self.settings, &self.platforms); + if let Err(err) = save_settings(&self.settings, &self.platforms){ + eprintln!("Failed to save settings: {:?}",err); + } } }); } @@ -341,17 +346,17 @@ fn setup(ctx: &egui::Context) { create_style(&mut style); ctx.set_style(style); } -pub fn run_sync() { - let mut app = MyEguiApp::new(); +pub fn run_sync() -> eyre::Result<()>{ + let mut app = MyEguiApp::new()?; while !all_ready(&app.games_to_sync) { println!("Finding games, trying again in 500ms"); std::thread::sleep(Duration::from_secs_f32(0.5)); } - app.run_sync(true); + app.run_sync_blocking() } -pub fn run_ui(args: Vec) { - let app = MyEguiApp::new(); +pub fn run_ui(args: Vec) -> eyre::Result<()>{ + let app = MyEguiApp::new()?; let no_v_sync = args.contains(&"--no-vsync".to_string()); let fullscreen = is_fullscreen(&args); let native_options = eframe::NativeOptions { @@ -370,6 +375,7 @@ pub fn run_ui(args: Vec) { Box::new(app) }), ); + Ok(()) } fn is_fullscreen(args: &[String]) -> bool { @@ -377,5 +383,5 @@ fn is_fullscreen(args: &[String]) -> bool { Ok(value) => !value.is_empty(), Err(_) => false, }; - is_steam_mode || args.contains(&"--fullscreen".to_string()) + is_steam_mode || args.contains(&"--fullscreen".to_string()) }