diff --git a/.gitignore b/.gitignore index 947af84..113797d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /tmp tests/root3/game5/data-symlink *~ +.idea/ diff --git a/src/cli/report.rs b/src/cli/report.rs index 7065eb9..f9a010b 100644 --- a/src/cli/report.rs +++ b/src/cli/report.rs @@ -543,6 +543,7 @@ mod tests { use super::*; use crate::{ + resource::manifest::Store, scan::{registry_compat::RegistryItem, ScannedFile, ScannedRegistry}, testing::s, }; @@ -598,6 +599,7 @@ Overall: change: Default::default(), container: None, redirected: None, + store: None, }, ScannedFile { path: StrictPath::new(s("/file2")), @@ -608,6 +610,7 @@ Overall: change: Default::default(), container: None, redirected: None, + store: None, }, }, found_registry_keys: hash_set! { @@ -619,7 +622,7 @@ Overall: }, &BackupInfo { failed_files: hash_set! { - ScannedFile::new("/file2", 51_200, "2"), + ScannedFile::new("/file2", 51_200, "2", None), }, failed_registry: hash_set! { RegistryItem::new(s("HKEY_CURRENT_USER/Key1")) @@ -667,6 +670,7 @@ Overall: change: ScanChange::Same, container: None, redirected: None, + store: None, }, }, found_registry_keys: hash_set! {}, @@ -693,6 +697,7 @@ Overall: change: Default::default(), container: None, redirected: None, + store: None, }, }, found_registry_keys: hash_set! {}, @@ -742,6 +747,7 @@ Overall: change: Default::default(), container: None, redirected: None, + store: None, }, ScannedFile { path: StrictPath::new(format!("{}/backup/file2", drive())), @@ -752,6 +758,7 @@ Overall: change: Default::default(), container: None, redirected: None, + store: None, }, }, found_registry_keys: hash_set! {}, @@ -788,7 +795,7 @@ Overall: &ScanInfo { game_name: s(name), found_files: hash_set! { - ScannedFile::new("/file1", 102_400, "1").change_as(ScanChange::New), + ScannedFile::new("/file1", 102_400, "1", None).change_as(ScanChange::New), }, found_registry_keys: hash_set! { ScannedRegistry::new("HKEY_CURRENT_USER/Key1").change_as(ScanChange::New), @@ -804,7 +811,7 @@ Overall: &ScanInfo { game_name: s("foo"), found_files: hash_set! { - ScannedFile::new("/file1", 102_400, "1"), + ScannedFile::new("/file1", 102_400, "1", None), }, found_registry_keys: hash_set! { ScannedRegistry::new("HKEY_CURRENT_USER/Key1"), @@ -835,16 +842,17 @@ Overall: #[test] fn can_render_in_standard_mode_with_different_file_changes() { let mut reporter = Reporter::standard(); + let store = Option::::None; reporter.add_game( "foo", &ScanInfo { game_name: s("foo"), found_files: hash_set! { - ScannedFile::new(s("/new"), 1, "1".to_string()).change_as(ScanChange::New), - ScannedFile::new(s("/different"), 1, "1".to_string()).change_as(ScanChange::Different), - ScannedFile::new(s("/same"), 1, "1".to_string()).change_as(ScanChange::Same), - ScannedFile::new(s("/unknown"), 1, "1".to_string()).change_as(ScanChange::Unknown), + ScannedFile::new(s("/new"), 1, "1".to_string(), store).change_as(ScanChange::New), + ScannedFile::new(s("/different"), 1, "1".to_string(), store).change_as(ScanChange::Different), + ScannedFile::new(s("/same"), 1, "1".to_string(), store).change_as(ScanChange::Same), + ScannedFile::new(s("/unknown"), 1, "1".to_string(), store).change_as(ScanChange::Unknown), }, found_registry_keys: hash_set! {}, ..Default::default() @@ -861,7 +869,7 @@ Overall: &ScanInfo { game_name: s("bar"), found_files: hash_set! { - ScannedFile::new(s("/brand-new"), 1, "1".to_string()).change_as(ScanChange::New), + ScannedFile::new(s("/brand-new"), 1, "1".to_string(), None).change_as(ScanChange::New), }, found_registry_keys: hash_set! {}, ..Default::default() @@ -937,8 +945,8 @@ Overall: &ScanInfo { game_name: s("foo"), found_files: hash_set! { - ScannedFile::new("/file1", 100, "1"), - ScannedFile::new("/file2", 50, "2"), + ScannedFile::new("/file1", 100, "1", None), + ScannedFile::new("/file2", 50, "2", None), }, found_registry_keys: hash_set! { ScannedRegistry::new("HKEY_CURRENT_USER/Key1"), @@ -949,7 +957,7 @@ Overall: }, &BackupInfo { failed_files: hash_set! { - ScannedFile::new("/file2", 50, "2"), + ScannedFile::new("/file2", 50, "2", None), }, failed_registry: hash_set! { RegistryItem::new(s("HKEY_CURRENT_USER/Key1")) @@ -1035,6 +1043,7 @@ Overall: change: Default::default(), container: None, redirected: None, + store: None, }, ScannedFile { path: StrictPath::new(format!("{}/backup/file2", drive())), @@ -1045,6 +1054,7 @@ Overall: change: Default::default(), container: None, redirected: None, + store: None, }, }, found_registry_keys: hash_set! {}, @@ -1103,7 +1113,7 @@ Overall: &ScanInfo { game_name: s(name), found_files: hash_set! { - ScannedFile::new("/file1", 102_400, "1"), + ScannedFile::new("/file1", 102_400, "1", None), }, found_registry_keys: hash_set! { ScannedRegistry::new("HKEY_CURRENT_USER/Key1"), @@ -1119,7 +1129,7 @@ Overall: &ScanInfo { game_name: s("foo"), found_files: hash_set! { - ScannedFile::new("/file1", 100, "2"), + ScannedFile::new("/file1", 100, "2", None), }, found_registry_keys: hash_set! { ScannedRegistry::new("HKEY_CURRENT_USER/Key1"), @@ -1178,16 +1188,17 @@ Overall: #[test] fn can_render_in_json_mode_with_different_file_changes() { let mut reporter = Reporter::json(); + let store = None; reporter.add_game( "foo", &ScanInfo { game_name: s("foo"), found_files: hash_set! { - ScannedFile::new("/new", 1, "1").change_as(ScanChange::New), - ScannedFile::new("/different", 1, "2").change_as(ScanChange::Different), - ScannedFile::new("/same", 1, "2").change_as(ScanChange::Same), - ScannedFile::new("/unknown", 1, "2").change_as(ScanChange::Unknown), + ScannedFile::new("/new", 1, "1", store).change_as(ScanChange::New), + ScannedFile::new("/different", 1, "2", store).change_as(ScanChange::Different), + ScannedFile::new("/same", 1, "2", store).change_as(ScanChange::Same), + ScannedFile::new("/unknown", 1, "2", store).change_as(ScanChange::Unknown), }, found_registry_keys: hash_set! {}, ..Default::default() diff --git a/src/gui/app.rs b/src/gui/app.rs index b083195..740bccf 100644 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -1797,6 +1797,15 @@ impl Application for App { search.change.choice = filter; Command::none() } + Message::EditedSearchFilterStore(filter) => { + let search = if self.screen == Screen::Backup { + &mut self.backup_screen.log.search + } else { + &mut self.restore_screen.log.search + }; + search.store.choice = filter; + Command::none() + } Message::EditedSortKey { screen, value } => { match screen { Screen::Backup => { diff --git a/src/gui/common.rs b/src/gui/common.rs index 500fed2..35748b6 100644 --- a/src/gui/common.rs +++ b/src/gui/common.rs @@ -176,6 +176,7 @@ pub enum Message { EditedSearchFilterCompleteness(game_filter::Completeness), EditedSearchFilterEnablement(game_filter::Enablement), EditedSearchFilterChange(game_filter::Change), + EditedSearchFilterStore(Store), EditedSortKey { screen: Screen, value: SortKey, diff --git a/src/gui/game_list.rs b/src/gui/game_list.rs index d40dbdc..fd9812a 100644 --- a/src/gui/game_list.rs +++ b/src/gui/game_list.rs @@ -20,7 +20,7 @@ use crate::{ resource::{ cache::Cache, config::{Config, Sort}, - manifest::{Manifest, Os}, + manifest::{Manifest, Os, Store}, }, scan::{layout::GameLayout, BackupInfo, DuplicateDetector, OperationStatus, ScanChange, ScanInfo}, }; @@ -37,6 +37,7 @@ pub struct GameListEntry { /// The `scan_info` gets mutated in response to things like toggling saves off, /// so we need a persistent flag to say if the game has been scanned yet. pub scanned: bool, + pub store: Option, } impl GameListEntry { diff --git a/src/gui/screen.rs b/src/gui/screen.rs index 2ae8a05..de3a842 100644 --- a/src/gui/screen.rs +++ b/src/gui/screen.rs @@ -46,7 +46,11 @@ fn make_status_row<'a>(status: &OperationStatus, duplication: Duplication) -> Ro .push(text(TRANSLATOR.processed_games(status)).size(size)) .push_if( || status.changed_games.new > 0, - || Badge::new_entry_with_count(status.changed_games.new).view(), + || { + Badge::new_entry_with_count(status.changed_games.new) + .on_press(Message::EditedSearchFilterChange(crate::scan::game_filter::Change::New)) + .view() + }, ) .push_if( || status.changed_games.different > 0, diff --git a/src/gui/search.rs b/src/gui/search.rs index 233100c..514ca3c 100644 --- a/src/gui/search.rs +++ b/src/gui/search.rs @@ -8,6 +8,7 @@ use crate::{ widget::{checkbox, pick_list, text, Column, Element, IcedParentExt, Row}, }, lang::TRANSLATOR, + resource::manifest::Store, scan::{ game_filter::{self, FilterKind}, Duplication, ScanInfo, @@ -28,6 +29,7 @@ pub struct FilterComponent { pub completeness: Filter, pub enablement: Filter, pub change: Filter, + pub store: Filter, } fn template<'a, T: 'static + Default + Copy + Eq + PartialEq + ToString>( @@ -66,8 +68,9 @@ impl FilterComponent { let complete = !self.completeness.active || self.completeness.choice.qualifies(scan); let enable = !show_deselected_games || !self.enablement.active || self.enablement.choice.qualifies(enabled); let changed = !self.change.active || self.change.choice.qualifies(scan); + let store = !self.store.active || self.store.choice.qualifies(scan); - fuzzy && unique && complete && changed && enable + fuzzy && unique && complete && changed && store && enable } pub fn toggle_filter(&mut self, filter: FilterKind, enabled: bool) { @@ -76,6 +79,7 @@ impl FilterComponent { FilterKind::Completeness => self.completeness.active = enabled, FilterKind::Enablement => self.enablement.active = enabled, FilterKind::Change => self.change.active = enabled, + FilterKind::Store => self.store.active = enabled, } } @@ -119,6 +123,12 @@ impl FilterComponent { game_filter::Change::ALL, Message::EditedSearchFilterChange, )) + .push(template( + &self.store, + FilterKind::Store, + Store::ALL, + Message::EditedSearchFilterStore, + )) .push_if( || show_deselected_games, || { diff --git a/src/path.rs b/src/path.rs index 7d2f999..d79ce85 100644 --- a/src/path.rs +++ b/src/path.rs @@ -5,7 +5,7 @@ use once_cell::sync::Lazy; use crate::{ prelude::{AnyError, SKIP}, - resource::manifest::Os, + resource::manifest::{Os, Store}, }; #[cfg(target_os = "windows")] @@ -275,6 +275,38 @@ pub fn resolve(raw: impl Into) -> Result { Ok(path) } +/// Stores metadata related to the path. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct PathMetadata { + is_case_sensitive: bool, + store: Option, +} + +impl PathMetadata { + pub fn new(is_case_sensitive: bool, store: Option) -> Self { + PathMetadata { + is_case_sensitive, + store, + } + } + + pub fn is_case_sensitive(&self) -> bool { + self.is_case_sensitive + } + + #[allow(dead_code)] + pub fn store(&self) -> Option { + self.store + } +} + +impl std::hash::Hash for PathMetadata { + fn hash(&self, state: &mut H) { + self.is_case_sensitive.hash(state); + self.store.hash(state); + } +} + /// This is a wrapper around paths to make it more obvious when we're /// converting between different representations. This also handles /// things like `~`. diff --git a/src/scan.rs b/src/scan.rs index 743755a..864c23b 100644 --- a/src/scan.rs +++ b/src/scan.rs @@ -18,7 +18,7 @@ use std::collections::{HashMap, HashSet}; pub use self::{backup::*, change::*, duplicate::*, launchers::*, preview::*, saves::*, steam::*, title::*}; use crate::{ - path::{CommonPath, StrictPath}, + path::{CommonPath, PathMetadata, StrictPath}, prelude::{filter_map_walkdir, Error, SKIP}, resource::{ config::{BackupFilter, RedirectConfig, RedirectKind, RootsConfig, SortKey, ToggledPaths, ToggledRegistry}, @@ -114,7 +114,7 @@ pub fn parse_paths( manifest_dir: &StrictPath, steam_shortcut: Option<&SteamShortcut>, platform: Os, -) -> HashSet<(StrictPath, bool)> { +) -> HashSet<(StrictPath, PathMetadata)> { use crate::resource::manifest::placeholder::*; let mut paths = HashSet::new(); @@ -428,7 +428,12 @@ pub fn parse_paths( paths .iter() - .map(|(x, y)| (StrictPath::relative(x.to_string(), Some(manifest_dir.interpret())), *y)) + .map(|(x, y)| { + ( + StrictPath::relative(x.to_string(), Some(manifest_dir.interpret())), + PathMetadata::new(*y, Some(root.store)), + ) + }) .collect() } @@ -452,7 +457,7 @@ pub fn scan_game_for_backup( #[allow(unused_mut)] let mut found_registry_keys = HashSet::new(); - let mut paths_to_check = HashSet::<(StrictPath, Option)>::new(); + let mut paths_to_check = HashSet::<(StrictPath, Option, Option)>::new(); // Add a dummy root for checking paths without ``. let mut roots_to_check: Vec = vec![RootsConfig { @@ -526,13 +531,13 @@ pub fn scan_game_for_backup( steam_shortcuts.get(name), platform, ); - for (candidate, case_sensitive) in candidates { + for (candidate, metadata) in candidates { log::trace!("[{name}] parsed candidate: {}", candidate.raw()); if candidate.raw().contains('<') { // This covers `SKIP` and any other unmatched placeholders. continue; } - paths_to_check.insert((candidate, Some(case_sensitive))); + paths_to_check.insert((candidate, Some(metadata.is_case_sensitive()), root.store.into())); } } } @@ -545,6 +550,7 @@ pub fn scan_game_for_backup( Some(manifest_dir_interpreted.clone()), ), None, + Some(Store::Steam), )); // Screenshots: @@ -555,6 +561,7 @@ pub fn scan_game_for_backup( Some(manifest_dir_interpreted.clone()), ), None, + Some(Store::Steam), )); } @@ -564,6 +571,7 @@ pub fn scan_game_for_backup( paths_to_check.insert(( StrictPath::relative(format!("{}/*.reg", prefix), Some(manifest_dir_interpreted.clone())), None, + Some(Store::Steam), )); } } @@ -582,7 +590,7 @@ pub fn scan_game_for_backup( }) .unwrap_or_default(); - for (path, case_sensitive) in paths_to_check { + for (path, case_sensitive, store) in paths_to_check { log::trace!("[{name}] checking: {}", path.raw()); if filter.is_path_ignored(&path) { log::debug!("[{name}] excluded: {}", path.raw()); @@ -612,6 +620,7 @@ pub fn scan_game_for_backup( original_path: None, ignored, container: None, + store, }); } else if p.is_dir() { log::trace!("[{name}] looking for files in: {}", p.raw()); @@ -649,6 +658,7 @@ pub fn scan_game_for_backup( original_path: None, ignored, container: None, + store, }); } } @@ -683,6 +693,7 @@ pub fn scan_game_for_backup( original_path: None, ignored: ignored_paths.is_ignored(name, previous_file), container: None, + store: None, }); } } @@ -778,13 +789,14 @@ pub fn scan_game_for_backup( game_name: name.to_string(), found_files, found_registry_keys, + // TODO: Add store from root ..Default::default() } } fn scan_game_for_backup_add_prefix( roots_to_check: &mut Vec, - paths_to_check: &mut HashSet<(StrictPath, Option)>, + paths_to_check: &mut HashSet<(StrictPath, Option, Option)>, wp: &StrictPath, manifest_dir_interpreted: &str, has_registry: bool, @@ -800,6 +812,7 @@ fn scan_game_for_backup_add_prefix( Some(manifest_dir_interpreted.to_owned()), ), None, + Some(Store::OtherWine), )); } } @@ -869,6 +882,10 @@ mod tests { testing::{repo, s, EMPTY_HASH}, }; + fn default_store() -> Option { + Some(Store::Other) + } + fn config() -> Config { Config::load_from_string(&format!( r#" @@ -934,8 +951,8 @@ mod tests { ScanInfo { game_name: s("game1"), found_files: hash_set! { - ScannedFile::new(format!("{}/tests/root1/game1/subdir/file2.txt", repo()), 2, "9d891e731f75deae56884d79e9816736b7488080").change_new(), - ScannedFile::new(format!("{}/tests/root2/game1/file1.txt", repo()), 1, "3a52ce780950d4d969792a2559cd519d7ee8c727").change_new(), + ScannedFile::new(format!("{}/tests/root1/game1/subdir/file2.txt", repo()), 2, "9d891e731f75deae56884d79e9816736b7488080", default_store()).change_new(), + ScannedFile::new(format!("{}/tests/root2/game1/file1.txt", repo()), 1, "3a52ce780950d4d969792a2559cd519d7ee8c727", default_store()).change_new(), }, found_registry_keys: hash_set! {}, ..Default::default() @@ -960,7 +977,7 @@ mod tests { ScanInfo { game_name: s("game 2"), found_files: hash_set! { - ScannedFile::new(format!("{}/tests/root2/game2/file1.txt", repo()), 1, "3a52ce780950d4d969792a2559cd519d7ee8c727").change_new(), + ScannedFile::new(format!("{}/tests/root2/game2/file1.txt", repo()), 1, "3a52ce780950d4d969792a2559cd519d7ee8c727", default_store()).change_new(), }, found_registry_keys: hash_set! {}, ..Default::default() @@ -992,7 +1009,7 @@ mod tests { ScanInfo { game_name: s("game5"), found_files: hash_set! { - ScannedFile::new(format!("{}/tests/root3/game5/data/file1.txt", repo()), 1, "3a52ce780950d4d969792a2559cd519d7ee8c727").change_new(), + ScannedFile::new(format!("{}/tests/root3/game5/data/file1.txt", repo()), 1, "3a52ce780950d4d969792a2559cd519d7ee8c727", default_store()).change_new(), }, found_registry_keys: hash_set! {}, ..Default::default() @@ -1024,7 +1041,7 @@ mod tests { ScanInfo { game_name: s("game 2"), found_files: hash_set! { - ScannedFile::new(format!("{}/tests/root3/game_2/file1.txt", repo()), 1, "3a52ce780950d4d969792a2559cd519d7ee8c727").change_new(), + ScannedFile::new(format!("{}/tests/root3/game_2/file1.txt", repo()), 1, "3a52ce780950d4d969792a2559cd519d7ee8c727", default_store()).change_new(), }, found_registry_keys: hash_set! {}, ..Default::default() @@ -1053,14 +1070,16 @@ mod tests { path: StrictPath::new(format!("{}/tests/home", repo())), store: Store::OtherHome, }]; + let store = Some(Store::OtherHome); + assert_eq!( ScanInfo { game_name: s("game4"), found_files: hash_set! { - ScannedFile::new(format!("{}/tests/home/data.txt", repo()), 0, EMPTY_HASH).change_new(), - ScannedFile::new(format!("{}/tests/home/AppData/Roaming/winAppData.txt", repo()), 0, EMPTY_HASH).change_new(), - ScannedFile::new(format!("{}/tests/home/AppData/Local/winLocalAppData.txt", repo()), 0, EMPTY_HASH).change_new(), - ScannedFile::new(format!("{}/tests/home/Documents/winDocuments.txt", repo()), 0, EMPTY_HASH).change_new(), + ScannedFile::new(format!("{}/tests/home/data.txt", repo()), 0, EMPTY_HASH, store).change_new(), + ScannedFile::new(format!("{}/tests/home/AppData/Roaming/winAppData.txt", repo()), 0, EMPTY_HASH, store).change_new(), + ScannedFile::new(format!("{}/tests/home/AppData/Local/winLocalAppData.txt", repo()), 0, EMPTY_HASH, store).change_new(), + ScannedFile::new(format!("{}/tests/home/Documents/winDocuments.txt", repo()), 0, EMPTY_HASH, store).change_new(), }, found_registry_keys: hash_set! {}, ..Default::default() @@ -1089,13 +1108,16 @@ mod tests { path: StrictPath::new(format!("{}/tests/home", repo())), store: Store::OtherHome, }]; + + let store = Some(Store::OtherHome); + assert_eq!( ScanInfo { game_name: s("game4"), found_files: hash_set! { - ScannedFile::new(format!("{}/tests/home/data.txt", repo()), 0, EMPTY_HASH).change_new(), - ScannedFile::new(format!("{}/tests/home/.config/xdgConfig.txt", repo()), 0, EMPTY_HASH).change_new(), - ScannedFile::new(format!("{}/tests/home/.local/share/xdgData.txt", repo()), 0, EMPTY_HASH).change_new(), + ScannedFile::new(format!("{}/tests/home/data.txt", repo()), 0, EMPTY_HASH, store).change_new(), + ScannedFile::new(format!("{}/tests/home/.config/xdgConfig.txt", repo()), 0, EMPTY_HASH, store).change_new(), + ScannedFile::new(format!("{}/tests/home/.local/share/xdgData.txt", repo()), 0, EMPTY_HASH, store).change_new(), }, found_registry_keys: hash_set! {}, ..Default::default() @@ -1123,7 +1145,7 @@ mod tests { ScanInfo { game_name: s("game4"), found_files: hash_set! { - ScannedFile::new(format!("{}/tests/wine-prefix/drive_c/users/anyone/data.txt", repo()), 0, EMPTY_HASH).change_new(), + ScannedFile::new(format!("{}/tests/wine-prefix/drive_c/users/anyone/data.txt", repo()), 0, EMPTY_HASH, Some(Store::OtherWine)).change_new(), }, found_registry_keys: hash_set! {}, ..Default::default() @@ -1151,7 +1173,7 @@ mod tests { ScanInfo { game_name: s("fake-registry"), found_files: hash_set! { - ScannedFile::new(format!("{}/tests/wine-prefix/user.reg", repo()), 37, "4a5b7e9de7d84ffb4bb3e9f38667f85741d5fbc0",).change_new(), + ScannedFile::new(format!("{}/tests/wine-prefix/user.reg", repo()), 37, "4a5b7e9de7d84ffb4bb3e9f38667f85741d5fbc0", Some(Store::OtherWine)).change_new(), }, found_registry_keys: hash_set! {}, ..Default::default() @@ -1183,7 +1205,7 @@ mod tests { }, ToggledPaths::default(), hash_set! { - ScannedFile::new(format!("{}/tests/root2/game1/file1.txt", repo()), 1, "3a52ce780950d4d969792a2559cd519d7ee8c727").change_new(), + ScannedFile::new(format!("{}/tests/root2/game1/file1.txt", repo()), 1, "3a52ce780950d4d969792a2559cd519d7ee8c727", default_store()).change_new(), }, ), ( @@ -1194,8 +1216,8 @@ mod tests { } }), hash_set! { - ScannedFile::new(format!("{}/tests/root1/game1/subdir/file2.txt", repo()), 2, "9d891e731f75deae56884d79e9816736b7488080").change_new().ignored(), - ScannedFile::new(format!("{}/tests/root2/game1/file1.txt", repo()), 1, "3a52ce780950d4d969792a2559cd519d7ee8c727").change_new(), + ScannedFile::new(format!("{}/tests/root1/game1/subdir/file2.txt", repo()), 2, "9d891e731f75deae56884d79e9816736b7488080", default_store()).change_new().ignored(), + ScannedFile::new(format!("{}/tests/root2/game1/file1.txt", repo()), 1, "3a52ce780950d4d969792a2559cd519d7ee8c727", default_store()).change_new(), }, ), ( @@ -1206,8 +1228,8 @@ mod tests { } }), hash_set! { - ScannedFile::new(format!("{}/tests/root1/game1/subdir/file2.txt", repo()), 2, "9d891e731f75deae56884d79e9816736b7488080").change_new().ignored(), - ScannedFile::new(format!("{}/tests/root2/game1/file1.txt", repo()), 1, "3a52ce780950d4d969792a2559cd519d7ee8c727").change_new(), + ScannedFile::new(format!("{}/tests/root1/game1/subdir/file2.txt", repo()), 2, "9d891e731f75deae56884d79e9816736b7488080", default_store()).change_new().ignored(), + ScannedFile::new(format!("{}/tests/root2/game1/file1.txt", repo()), 1, "3a52ce780950d4d969792a2559cd519d7ee8c727", default_store()).change_new(), }, ), ]; diff --git a/src/scan/duplicate.rs b/src/scan/duplicate.rs index 480a8b7..714e364 100644 --- a/src/scan/duplicate.rs +++ b/src/scan/duplicate.rs @@ -374,16 +374,17 @@ mod tests { use velcro::{hash_map, hash_set}; use super::*; - use crate::{scan::ScannedRegistry, testing::s}; + use crate::{resource::manifest::Store, scan::ScannedRegistry, testing::s}; #[test] fn can_add_games_in_backup_mode() { let mut detector = DuplicateDetector::default(); + let store = Some(Store::Other); let game1 = s("game1"); let game2 = s("game2"); - let file1 = ScannedFile::new("file1.txt", 1, "1"); - let file2 = ScannedFile::new("file2.txt", 2, "2"); + let file1 = ScannedFile::new("file1.txt", 1, "1", store); + let file2 = ScannedFile::new("file2.txt", 2, "2", store); let reg1 = s("reg1"); let reg2 = s("reg2"); @@ -462,6 +463,7 @@ mod tests { change: Default::default(), container: None, redirected: None, + store: None, }; let file1b = ScannedFile { path: StrictPath::new(s("file1b.txt")), @@ -472,6 +474,7 @@ mod tests { change: Default::default(), container: None, redirected: None, + store: None, }; detector.add_game( @@ -510,6 +513,7 @@ mod tests { change: Default::default(), container: None, redirected: None, + store: None, }) ); @@ -532,6 +536,7 @@ mod tests { change: Default::default(), container: None, redirected: None, + store: None, }) ); } diff --git a/src/scan/game_filter.rs b/src/scan/game_filter.rs index 3ca0aed..177fad4 100644 --- a/src/scan/game_filter.rs +++ b/src/scan/game_filter.rs @@ -1,5 +1,6 @@ use crate::{ lang::TRANSLATOR, + resource::manifest::Store, scan::{Duplication, ScanInfo}, }; @@ -11,6 +12,7 @@ pub enum FilterKind { Completeness, Enablement, Change, + Store, } #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] @@ -112,3 +114,11 @@ impl Change { } } } + +impl Store { + pub fn qualifies(&self, scan: &ScanInfo) -> bool { + let store_match = scan.found_files.iter().any(|ff| ff.store.is_some_and(|s| s == *self)); + + store_match + } +} diff --git a/src/scan/layout.rs b/src/scan/layout.rs index 6de6ba6..35c0170 100644 --- a/src/scan/layout.rs +++ b/src/scan/layout.rs @@ -699,6 +699,7 @@ impl GameLayout { redirected, original_path: Some(original_path), container: None, + store: None, }); } BackupFormat::Zip => { @@ -715,6 +716,7 @@ impl GameLayout { redirected, original_path: Some(original_path), container: Some(self.path.joined(&backup.name)), + store: None, }); } } @@ -754,6 +756,7 @@ impl GameLayout { redirected, original_path: Some(original_path), container: None, + store: None, }); } BackupFormat::Zip => { @@ -770,6 +773,7 @@ impl GameLayout { redirected, original_path: Some(original_path), container: Some(self.path.joined(&backup.name)), + store: None, }); } } @@ -811,6 +815,7 @@ impl GameLayout { ignored: false, container: None, redirected: None, + store: None, }); } } @@ -2990,6 +2995,7 @@ mod tests { change: Default::default(), container: None, redirected: None, + store: None, }, ScannedFile { path: make_restorable_path("backup-1", "file2.txt"), @@ -3000,6 +3006,7 @@ mod tests { change: Default::default(), container: None, redirected: None, + store: None, }, }, layout.restorable_files(&BackupId::Latest, false, &[], &Default::default()), @@ -3040,6 +3047,7 @@ mod tests { change: Default::default(), container: Some(make_path("backup-1.zip")), redirected: None, + store: None, }, ScannedFile { path: make_restorable_path_zip("file2.txt"), @@ -3050,6 +3058,7 @@ mod tests { change: Default::default(), container: Some(make_path("backup-1.zip")), redirected: None, + store: None, }, }, layout.restorable_files(&BackupId::Latest, false, &[], &Default::default()), @@ -3101,6 +3110,7 @@ mod tests { change: Default::default(), container: None, redirected: None, + store: None, }, ScannedFile { path: make_restorable_path("backup-2", "changed.txt"), @@ -3111,6 +3121,7 @@ mod tests { change: Default::default(), container: None, redirected: None, + store: None, }, ScannedFile { path: make_restorable_path("backup-2", "added.txt"), @@ -3121,6 +3132,7 @@ mod tests { change: Default::default(), container: None, redirected: None, + store: None, }, }, layout.restorable_files(&BackupId::Latest, false, &[], &Default::default()), @@ -3172,6 +3184,7 @@ mod tests { change: Default::default(), container: Some(make_path("backup-1.zip")), redirected: None, + store: None, }, ScannedFile { path: make_restorable_path_zip("changed.txt"), @@ -3182,6 +3195,7 @@ mod tests { change: Default::default(), container: Some(make_path("backup-2.zip")), redirected: None, + store: None, }, ScannedFile { path: make_restorable_path_zip("added.txt"), @@ -3192,6 +3206,7 @@ mod tests { change: Default::default(), container: Some(make_path("backup-2.zip")), redirected: None, + store: None, }, }, layout.restorable_files(&BackupId::Latest, false, &[], &Default::default()), @@ -3281,6 +3296,7 @@ mod tests { change: ScanChange::New, container: None, redirected: None, + store: None, }, ScannedFile { path: restorable_file_simple(".", "file2.txt"), @@ -3291,6 +3307,7 @@ mod tests { change: ScanChange::New, container: None, redirected: None, + store: None, }, }, available_backups: backups.clone(), diff --git a/src/scan/saves.rs b/src/scan/saves.rs index f90357f..a0cfcb5 100644 --- a/src/scan/saves.rs +++ b/src/scan/saves.rs @@ -2,6 +2,7 @@ use std::collections::BTreeMap; use crate::{ prelude::StrictPath, + resource::manifest::Store, scan::{registry_compat::RegistryItem, ScanChange}, }; @@ -20,11 +21,13 @@ pub struct ScannedFile { /// An enclosing archive file, if any, depending on the `BackupFormat`. pub container: Option, pub redirected: Option, + /// Optional. The store the file originated from. + pub store: Option, } impl ScannedFile { #[cfg(test)] - pub fn new + ToString, H: ToString>(path: T, size: u64, hash: H) -> Self { + pub fn new + ToString, H: ToString>(path: T, size: u64, hash: H, store: Option) -> Self { Self { path: StrictPath::new(path.to_string()), size, @@ -34,6 +37,7 @@ impl ScannedFile { change: Default::default(), container: None, redirected: None, + store, } } @@ -56,6 +60,7 @@ impl ScannedFile { change, container: None, redirected: None, + store: None, } }