From 967465d32642fcc0d03a20fd7eba684c8cb5336c Mon Sep 17 00:00:00 2001 From: Philip Kristoffersen Date: Fri, 20 May 2022 21:53:21 +0200 Subject: [PATCH] Backup shortcuts (#144) * Add initial option to back up shortcuts * Show list of backups * Reload backups on change * Restore backups * Improve UX of backup ui * Reorganize ui * Update version in cargo.toml * Update flatpak description * Update cargo-lock.json --- Cargo.lock | 26 +++- Cargo.toml | 72 ++++++++---- flatpak/cargo-lock.json | 26 ++++ flatpak/io.github.philipk.boilr.appdata.xml | 9 +- src/config.rs | 6 + src/ui/mod.rs | 2 + src/ui/ui_backup.rs | 124 ++++++++++++++++++++ src/ui/ui_import_games.rs | 6 +- src/ui/uiapp.rs | 27 ++++- 9 files changed, 265 insertions(+), 33 deletions(-) create mode 100644 src/ui/ui_backup.rs diff --git a/Cargo.lock b/Cargo.lock index 2eaffd7..ed7bd6f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -148,9 +148,10 @@ checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" [[package]] name = "boilr" -version = "1.3.6" +version = "1.3.7" dependencies = [ "base64", + "chrono", "config", "copypasta", "dashmap", @@ -269,6 +270,19 @@ dependencies = [ "libc", ] +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits 0.2.15", + "time", + "winapi", +] + [[package]] name = "clipboard-win" version = "3.1.1" @@ -2542,6 +2556,16 @@ dependencies = [ "weezl", ] +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "tinyvec" version = "1.6.0" diff --git a/Cargo.toml b/Cargo.toml index ea0c7b2..48c559f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,35 +1,61 @@ [package] -name = "boilr" -version = "1.3.6" edition = "2021" +name = "boilr" +version = "1.3.7" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -steam_shortcuts_util = "1.1.7" -steamgriddb_api = "^0.3.0" -serde = { version = "^1.0.137", features = ["derive"] } -serde_json = "^1.0.81" -tokio = { version = "^1.18.2", features = ["full"] } -reqwest = { version = "^0.11.10", default_features = false } +base64 = "^0.13.0" +chrono = "^0.4.19" config = "^0.11.0" +copypasta = "0.7.1" failure = "^0.1.8" -nom = "^7.1.1" flate2 = "^1.0.23" -futures = { version = "^0.3.21" } -dashmap = { version = "^5.3.3", features = ["serde"] } is_executable = "^1.0.1" +nom = "^7.1.1" rusty-leveldb = "^0.3.6" -base64 = "^0.13.0" -eframe = { version = "^0.18.0" } -egui = { version = "^0.18.1" } -image = { version = "0.24.2", features = ["png"] } -toml = { version = "^0.5.9" } +serde_json = "^1.0.81" +sqlite = "^0.26.0" +steam_shortcuts_util = "^1.1.7" +steamgriddb_api = "^0.3.0" sysinfo = "^0.23.12" -sqlite = "0.26.0" -copypasta = "0.7.1" -[target.'cfg(windows)'.dependencies] -winreg = "0.10.1" +[dependencies.dashmap] +features = ["serde"] +version = "^5.3.3" + +[dependencies.eframe] +version = "^0.18.0" + +[dependencies.egui] +version = "^0.18.1" + +[dependencies.futures] +version = "^0.3.21" + +[dependencies.image] +features = ["png"] +version = "^0.24.2" + +[dependencies.reqwest] +default_features = false +version = "^0.11.10" + +[dependencies.serde] +features = ["derive"] +version = "^1.0.137" + +[dependencies.tokio] +features = ["full"] +version = "^1.18.2" + +[dependencies.toml] +version = "^0.5.9" + +[target] +[target."cfg(windows)"] +[target."cfg(windows)".build-dependencies] +winres = "^0.1" + +[target."cfg(windows)".dependencies] +winreg = "^0.10.1" -[target.'cfg(windows)'.build-dependencies] -winres = "0.1" diff --git a/flatpak/cargo-lock.json b/flatpak/cargo-lock.json index 86d79cc..2ac468f 100644 --- a/flatpak/cargo-lock.json +++ b/flatpak/cargo-lock.json @@ -402,6 +402,19 @@ "dest": "cargo/vendor/cgl-0.3.2", "dest-filename": ".cargo-checksum.json" }, + { + "type": "archive", + "archive-type": "tar-gzip", + "url": "https://static.crates.io/crates/chrono/chrono-0.4.19.crate", + "sha256": "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73", + "dest": "cargo/vendor/chrono-0.4.19" + }, + { + "type": "inline", + "contents": "{\"package\": \"670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73\", \"files\": {}}", + "dest": "cargo/vendor/chrono-0.4.19", + "dest-filename": ".cargo-checksum.json" + }, { "type": "archive", "archive-type": "tar-gzip", @@ -3327,6 +3340,19 @@ "dest": "cargo/vendor/tiff-0.7.2", "dest-filename": ".cargo-checksum.json" }, + { + "type": "archive", + "archive-type": "tar-gzip", + "url": "https://static.crates.io/crates/time/time-0.1.43.crate", + "sha256": "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438", + "dest": "cargo/vendor/time-0.1.43" + }, + { + "type": "inline", + "contents": "{\"package\": \"ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438\", \"files\": {}}", + "dest": "cargo/vendor/time-0.1.43", + "dest-filename": ".cargo-checksum.json" + }, { "type": "archive", "archive-type": "tar-gzip", diff --git a/flatpak/io.github.philipk.boilr.appdata.xml b/flatpak/io.github.philipk.boilr.appdata.xml index ed9d9ca..3e26ef3 100644 --- a/flatpak/io.github.philipk.boilr.appdata.xml +++ b/flatpak/io.github.philipk.boilr.appdata.xml @@ -10,7 +10,7 @@

This little tool will show games from other games platforms in your Steam library. It uses the Steam 3rd party shortcuts feature and does not require you to set up anything. The goal is that you do not have to leave your Steam library to launch games from other launchers/stores, so that you can find all the games that you have available. - Optionally you can set BoilR up to automatically download artwork from SteamGridDB.

+ You can also use BoilR to manually and automatically download custom art from SteamGridDB.

@@ -25,7 +25,12 @@ https://hughsie.github.io/oars/index.html --> - + + +

Back up your shortcuts and restore them later

+
+
+

Automatically find Steam installed through flatpak

diff --git a/src/config.rs b/src/config.rs index a9d413a..5991f64 100644 --- a/src/config.rs +++ b/src/config.rs @@ -37,6 +37,12 @@ pub fn get_cache_file() -> PathBuf { get_config_folder().join("cache.json").to_path_buf() } +pub fn get_backups_flder() -> PathBuf { + let backups_path = get_config_folder().join("backup"); + let _ = create_dir_all(&backups_path); + backups_path.to_path_buf() +} + #[cfg(target_family = "unix")] pub fn get_boilr_links_path() -> PathBuf { get_config_folder().join("links").to_path_buf() diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 1c2b6d0..051b7de 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,10 +1,12 @@ mod defines; +mod ui_backup; mod ui_image_download; mod ui_import_games; mod ui_settings; mod uiapp; pub use defines::*; +pub use ui_backup::*; pub use ui_image_download::*; pub use ui_import_games::*; pub use ui_settings::*; diff --git a/src/ui/ui_backup.rs b/src/ui/ui_backup.rs new file mode 100644 index 0000000..d122180 --- /dev/null +++ b/src/ui/ui_backup.rs @@ -0,0 +1,124 @@ +use std::path::{Path, PathBuf}; + +use chrono::Local; +use egui::ScrollArea; + +use crate::{ + config::get_backups_flder, + steam::{get_shortcuts_paths, SteamSettings}, +}; + +use super::MyEguiApp; + +#[derive(Default)] +pub struct BackupState { + pub available_backups: Option>, + pub last_restore: Option, +} + +impl MyEguiApp { + pub fn render_backup(&mut self, ui: &mut egui::Ui) { + ui.heading("Backups"); + ui.label("Here you can restore backed up shortcuts files"); + ui.label("Click a backup to restore it, your current shortcuts will be backed up first"); + ui.add_space(15.0); + + if let Some(last_restore) = self.backup_state.last_restore.as_ref() { + ui.heading(format!("Last restored {:?}", last_restore)); + } + + if ui.button("Click here to create a new backup").clicked() { + backup_shortcuts(&self.settings.steam); + self.backup_state.available_backups = None; + } + + let available_backups = self + .backup_state + .available_backups + .get_or_insert_with(|| load_backups()); + + if available_backups.is_empty() { + ui.label("No backups found, they will be created every time you run import"); + } else { + ScrollArea::vertical() + .stick_to_right() + .auto_shrink([false, true]) + .show(ui, |ui| { + for backup_path in available_backups { + if ui + .button(backup_path.to_string_lossy().to_string()) + .clicked() + { + //Restore + backup_shortcuts(&self.settings.steam); + if restore_backup(&self.settings.steam, backup_path.as_path()) { + self.backup_state.last_restore = Some(backup_path.clone()); + } + } + } + }); + } + + + } +} + +pub fn restore_backup(steam_settings: &SteamSettings, shortcut_path: &Path) -> bool { + let file_name = shortcut_path.file_name().unwrap(); + let paths = get_shortcuts_paths(steam_settings); + if let Ok(paths) = paths { + 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); + return true; + } + } + } + } + return false; +} + +pub fn load_backups() -> Vec { + let backup_folder = get_backups_flder(); + let files = std::fs::read_dir(&backup_folder); + let mut result = vec![]; + if let Ok(files) = files { + for file in files { + if let Ok(file) = file { + if file + .path() + .extension() + .unwrap_or_default() + .to_string_lossy() + == "vdf" + { + result.push(file.path().to_path_buf()); + } + } + } + } + result.sort(); + result.reverse(); + return result; +} + +pub fn backup_shortcuts(steam_settings: &SteamSettings) { + let backup_folder = get_backups_flder(); + let paths = get_shortcuts_paths(&steam_settings); + let date = Local::now(); + let date_string = date.format("%Y-%m-%d-%H-%M-%S"); + if let Ok(user_infos) = 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(); + } + } + } +} diff --git a/src/ui/ui_import_games.rs b/src/ui/ui_import_games.rs index 736d2b4..228bafe 100644 --- a/src/ui/ui_import_games.rs +++ b/src/ui/ui_import_games.rs @@ -9,7 +9,7 @@ use crate::sync; use crate::sync::{download_images, SyncProgress}; -use super::ImageSelectState; +use super::{ImageSelectState, backup_shortcuts}; use super::{ ui_colors::{BACKGROUND_COLOR, EXTRA_BACKGROUND_COLOR}, MyEguiApp, @@ -42,6 +42,9 @@ impl FetcStatus { } impl MyEguiApp { + + + pub(crate) fn render_import_games(&mut self, ui: &mut egui::Ui) { ui.heading("Import Games"); @@ -118,6 +121,7 @@ impl MyEguiApp { self.rt.spawn_blocking(move || { MyEguiApp::save_settings_to_file(&settings); let mut some_sender = Some(sender); + backup_shortcuts(&settings.steam); let usersinfo = sync::run_sync(&settings, &mut some_sender).unwrap(); let task = download_images(&settings, &usersinfo, &mut some_sender); block_on(task); diff --git a/src/ui/uiapp.rs b/src/ui/uiapp.rs index 410c4b8..9dcc993 100644 --- a/src/ui/uiapp.rs +++ b/src/ui/uiapp.rs @@ -17,7 +17,7 @@ use super::{ }, ui_images::{get_import_image, get_logo, get_logo_icon}, ui_import_games::FetcStatus, - ImageSelectState, + BackupState, ImageSelectState, }; const SECTION_SPACING: f32 = 25.0; @@ -37,6 +37,7 @@ pub struct MyEguiApp { pub(crate) status_reciever: Receiver, pub(crate) epic_manifests: Option>, pub(crate) image_selected_state: ImageSelectState, + pub(crate) backup_state: BackupState, } impl MyEguiApp { @@ -51,6 +52,7 @@ impl MyEguiApp { status_reciever: watch::channel(SyncProgress::NotStarted).1, epic_manifests: None, image_selected_state: ImageSelectState::default(), + backup_state: BackupState::default(), } } } @@ -60,6 +62,7 @@ enum Menues { Import, Settings, Images, + Backup, } impl Default for Menues { @@ -82,19 +85,28 @@ impl App for MyEguiApp { ui.image(texture, size); ui.add_space(SECTION_SPACING); - let changed = ui + let mut changed = ui .selectable_value(&mut self.selected_menu, Menues::Import, "Import Games") .changed(); - let mut changed = changed - || ui - .selectable_value(&mut self.selected_menu, Menues::Settings, "Settings") - .changed(); if self.settings.steamgrid_db.auth_key.is_some() { changed = changed || ui .selectable_value(&mut self.selected_menu, Menues::Images, "Images") .changed(); } + changed = changed + || ui + .selectable_value(&mut self.selected_menu, Menues::Settings, "Settings") + .changed(); + + changed = changed + || ui + .selectable_value(&mut self.selected_menu, Menues::Backup, "Backup") + .changed(); + + if changed { + self.backup_state.available_backups = None; + } 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; @@ -155,6 +167,9 @@ impl App for MyEguiApp { Menues::Images => { self.render_ui_images(ui); } + Menues::Backup => { + self.render_backup(ui); + } }; }); }