supreme_fs/game_storage/
discovery.rs1use super::GameStorage;
4use std::io;
5use std::path::{Path, PathBuf};
6use walkdir::WalkDir;
7
8#[derive(PartialEq, Eq, Clone, Copy, Debug)]
9pub enum DiscoverMode {
10 All,
11 Files,
12 Directories,
13}
14
15impl GameStorage {
16 pub fn discover_relative(
22 &self,
23 rel_path: &Path,
24 mode: DiscoverMode,
25 ) -> io::Result<Vec<PathBuf>> {
26 let mut paths = Vec::new();
27 let full_path = self.root_path.join(rel_path);
28
29 if !full_path.exists() {
30 return Ok(paths);
31 }
32
33 for entry in WalkDir::new(&full_path)
34 .into_iter()
35 .filter_map(std::result::Result::ok)
36 {
37 let path = entry.path();
38
39 if path != full_path
40 && (mode == DiscoverMode::All
41 || (mode == DiscoverMode::Files && path.is_file())
42 || (mode == DiscoverMode::Directories && path.is_dir()))
43 {
44 paths.push(path.to_path_buf());
45 }
46 }
47 Ok(paths)
48 }
49
50 pub fn discover(&self, mode: DiscoverMode) -> io::Result<Vec<PathBuf>> {
56 let mut paths = Vec::new();
57 let root_path = &self.root_path;
58 for entry in WalkDir::new(root_path)
59 .into_iter()
60 .filter_map(std::result::Result::ok)
61 {
62 let path = entry.path();
63
64 if let Ok(rel_path) = path.strip_prefix(root_path) {
66 let is_hidden = rel_path
67 .components()
68 .any(|c| c.as_os_str().to_str().unwrap_or_default().starts_with('.'));
69 if is_hidden {
70 continue;
71 }
72 }
73
74 if path != root_path
75 && (mode == DiscoverMode::All
76 || (mode == DiscoverMode::Files && path.is_file())
77 || (mode == DiscoverMode::Directories && path.is_dir()))
78 {
79 paths.push(path.to_path_buf());
80 }
81 }
82 Ok(paths)
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89 use std::fs;
90 use tempfile::tempdir;
91
92 fn make_storage() -> (tempfile::TempDir, GameStorage) {
93 let dir = tempdir().expect("tempdir");
94 let storage = GameStorage::new(dir.path());
95 (dir, storage)
96 }
97
98 #[test]
99 fn discover_on_empty_directory_returns_empty() {
100 let (_dir, storage) = make_storage();
101 let paths = storage.discover(DiscoverMode::All).expect("discover");
102 assert!(paths.is_empty());
103 }
104
105 #[test]
106 fn discover_finds_files_and_directories() {
107 let (dir, storage) = make_storage();
108 fs::create_dir_all(dir.path().join("levels")).unwrap();
109 fs::write(dir.path().join("levels/level1.dat"), b"data").unwrap();
110 fs::write(dir.path().join("readme.txt"), b"hi").unwrap();
111
112 let all = storage.discover(DiscoverMode::All).expect("discover");
113 assert_eq!(all.len(), 3); let files = storage.discover(DiscoverMode::Files).expect("discover");
116 assert_eq!(files.len(), 2);
117 assert!(files.iter().all(|p| p.is_file()));
118
119 let dirs = storage
120 .discover(DiscoverMode::Directories)
121 .expect("discover");
122 assert_eq!(dirs.len(), 1);
123 assert!(dirs[0].ends_with("levels"));
124 }
125
126 #[test]
127 fn discover_skips_hidden_files_and_directories() {
128 let (dir, storage) = make_storage();
129 fs::create_dir_all(dir.path().join(".supreme")).unwrap();
130 fs::write(dir.path().join(".supreme/manifest.json"), b"{}").unwrap();
131 fs::write(dir.path().join("visible.txt"), b"hi").unwrap();
132
133 let all = storage.discover(DiscoverMode::All).expect("discover");
134 assert_eq!(all.len(), 1);
135 assert!(all[0].ends_with("visible.txt"));
136 }
137
138 #[test]
139 fn discover_relative_only_walks_requested_subdirectory() {
140 let (dir, storage) = make_storage();
141 fs::create_dir_all(dir.path().join("levels")).unwrap();
142 fs::write(dir.path().join("levels/level1.dat"), b"data").unwrap();
143 fs::write(dir.path().join("outside.txt"), b"data").unwrap();
144
145 let found = storage
146 .discover_relative(Path::new("levels"), DiscoverMode::Files)
147 .expect("discover_relative");
148
149 assert_eq!(found.len(), 1);
150 assert!(found[0].ends_with("level1.dat"));
151 }
152
153 #[test]
154 fn discover_relative_on_missing_directory_returns_empty_ok() {
155 let (_dir, storage) = make_storage();
156 let found = storage
157 .discover_relative(Path::new("does_not_exist"), DiscoverMode::All)
158 .expect("discover_relative should not error on missing dir");
159 assert!(found.is_empty());
160 }
161}