oximedia_proxy/conform/
mapper.rs1use crate::Result;
4use std::collections::HashMap;
5use std::path::{Path, PathBuf};
6
7pub struct PathMapper {
9 mappings: HashMap<PathBuf, PathBuf>,
11
12 proxy_base: Option<PathBuf>,
14
15 original_base: Option<PathBuf>,
17
18 case_insensitive: bool,
20}
21
22impl PathMapper {
23 #[must_use]
25 pub fn new() -> Self {
26 Self {
27 mappings: HashMap::new(),
28 proxy_base: None,
29 original_base: None,
30 case_insensitive: false,
31 }
32 }
33
34 #[must_use]
36 pub fn with_proxy_base(mut self, base: PathBuf) -> Self {
37 self.proxy_base = Some(base);
38 self
39 }
40
41 #[must_use]
43 pub fn with_original_base(mut self, base: PathBuf) -> Self {
44 self.original_base = Some(base);
45 self
46 }
47
48 #[must_use]
50 pub const fn case_insensitive(mut self, enabled: bool) -> Self {
51 self.case_insensitive = enabled;
52 self
53 }
54
55 pub fn add_mapping(&mut self, proxy: PathBuf, original: PathBuf) {
57 self.mappings.insert(proxy, original);
58 }
59
60 #[must_use]
62 pub fn map(&self, proxy_path: &Path) -> Option<PathBuf> {
63 if let Some(original) = self.mappings.get(proxy_path) {
65 return Some(original.clone());
66 }
67
68 if let (Some(proxy_base), Some(original_base)) = (&self.proxy_base, &self.original_base) {
70 if let Ok(relative) = proxy_path.strip_prefix(proxy_base) {
71 return Some(original_base.join(relative));
72 }
73 }
74
75 if self.case_insensitive {
77 let proxy_lower = proxy_path.to_string_lossy().to_lowercase();
78 for (key, value) in &self.mappings {
79 if key.to_string_lossy().to_lowercase() == proxy_lower {
80 return Some(value.clone());
81 }
82 }
83 }
84
85 None
86 }
87
88 #[must_use]
90 pub fn map_batch(&self, proxy_paths: &[PathBuf]) -> Vec<MappingResult> {
91 proxy_paths
92 .iter()
93 .map(|proxy| {
94 if let Some(original) = self.map(proxy) {
95 MappingResult::Success {
96 proxy: proxy.clone(),
97 original,
98 }
99 } else {
100 MappingResult::Failed {
101 proxy: proxy.clone(),
102 }
103 }
104 })
105 .collect()
106 }
107
108 pub fn clear(&mut self) {
110 self.mappings.clear();
111 }
112
113 #[must_use]
115 pub fn count(&self) -> usize {
116 self.mappings.len()
117 }
118
119 #[must_use]
121 pub fn proxy_paths(&self) -> Vec<PathBuf> {
122 self.mappings.keys().cloned().collect()
123 }
124
125 #[must_use]
127 pub fn original_paths(&self) -> Vec<PathBuf> {
128 self.mappings.values().cloned().collect()
129 }
130}
131
132impl Default for PathMapper {
133 fn default() -> Self {
134 Self::new()
135 }
136}
137
138#[derive(Debug, Clone)]
140pub enum MappingResult {
141 Success {
143 proxy: PathBuf,
145 original: PathBuf,
147 },
148 Failed {
150 proxy: PathBuf,
152 },
153}
154
155impl MappingResult {
156 #[must_use]
158 pub const fn is_success(&self) -> bool {
159 matches!(self, Self::Success { .. })
160 }
161
162 #[must_use]
164 pub fn proxy(&self) -> &Path {
165 match self {
166 Self::Success { proxy, .. } | Self::Failed { proxy } => proxy,
167 }
168 }
169}
170
171pub struct AutoPathMapper {
173 mapper: PathMapper,
174}
175
176impl AutoPathMapper {
177 #[must_use]
179 pub fn new() -> Self {
180 Self {
181 mapper: PathMapper::new(),
182 }
183 }
184
185 pub fn auto_detect(&mut self, proxy_dir: &Path, original_dir: &Path) -> Result<usize> {
187 let mut count = 0;
188
189 if let Ok(entries) = std::fs::read_dir(proxy_dir) {
191 for entry in entries.flatten() {
192 if let Ok(metadata) = entry.metadata() {
193 if metadata.is_file() {
194 let proxy_path = entry.path();
195
196 if let Some(original_path) =
198 self.find_matching_original(&proxy_path, original_dir)
199 {
200 self.mapper.add_mapping(proxy_path, original_path);
201 count += 1;
202 }
203 }
204 }
205 }
206 }
207
208 Ok(count)
209 }
210
211 fn find_matching_original(&self, proxy: &Path, original_dir: &Path) -> Option<PathBuf> {
213 let proxy_stem = proxy.file_stem()?.to_str()?;
215
216 if let Ok(entries) = std::fs::read_dir(original_dir) {
218 for entry in entries.flatten() {
219 if let Some(filename) = entry.file_name().to_str() {
220 if filename.contains(proxy_stem) {
222 return Some(entry.path());
223 }
224 }
225 }
226 }
227
228 None
229 }
230
231 #[must_use]
233 pub const fn mapper(&self) -> &PathMapper {
234 &self.mapper
235 }
236}
237
238impl Default for AutoPathMapper {
239 fn default() -> Self {
240 Self::new()
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 #[test]
249 fn test_path_mapper() {
250 let mut mapper = PathMapper::new();
251
252 mapper.add_mapping(PathBuf::from("proxy.mp4"), PathBuf::from("original.mov"));
253
254 let result = mapper.map(Path::new("proxy.mp4"));
255 assert!(result.is_some());
256 assert_eq!(
257 result.expect("should succeed in test"),
258 PathBuf::from("original.mov")
259 );
260
261 assert_eq!(mapper.count(), 1);
262 }
263
264 #[test]
265 fn test_path_mapper_with_bases() {
266 let mapper = PathMapper::new()
267 .with_proxy_base(PathBuf::from("/proxies"))
268 .with_original_base(PathBuf::from("/originals"));
269
270 let result = mapper.map(Path::new("/proxies/clip1.mp4"));
271 assert!(result.is_some());
272 assert_eq!(
273 result.expect("should succeed in test"),
274 PathBuf::from("/originals/clip1.mp4")
275 );
276 }
277
278 #[test]
279 fn test_mapping_result() {
280 let result = MappingResult::Success {
281 proxy: PathBuf::from("proxy.mp4"),
282 original: PathBuf::from("original.mov"),
283 };
284
285 assert!(result.is_success());
286 assert_eq!(result.proxy(), Path::new("proxy.mp4"));
287
288 let result = MappingResult::Failed {
289 proxy: PathBuf::from("proxy.mp4"),
290 };
291
292 assert!(!result.is_success());
293 }
294
295 #[test]
296 fn test_batch_mapping() {
297 let mut mapper = PathMapper::new();
298 mapper.add_mapping(PathBuf::from("proxy1.mp4"), PathBuf::from("original1.mov"));
299 mapper.add_mapping(PathBuf::from("proxy2.mp4"), PathBuf::from("original2.mov"));
300
301 let proxies = vec![
302 PathBuf::from("proxy1.mp4"),
303 PathBuf::from("proxy2.mp4"),
304 PathBuf::from("proxy3.mp4"),
305 ];
306
307 let results = mapper.map_batch(&proxies);
308 assert_eq!(results.len(), 3);
309 assert!(results[0].is_success());
310 assert!(results[1].is_success());
311 assert!(!results[2].is_success());
312 }
313
314 #[test]
315 fn test_auto_path_mapper() {
316 let auto_mapper = AutoPathMapper::new();
317 assert_eq!(auto_mapper.mapper().count(), 0);
318 }
319}