1use std::path::{Path, PathBuf};
8
9pub const ALIASES: [(&str, &str); 9] = [
11 ("jfif", "jpg"),
12 ("jpe", "jpg"),
13 ("insp", "jpg"),
14 ("psdt", "psd"),
15 ("hif", "heic"),
16 ("tif", "tiff"),
17 ("ait", "ai"),
18 ("indt", "indd"),
19 ("m2ts", "mts"),
20];
21
22pub fn canonical(ext: &str) -> String {
25 let lower = ext.to_ascii_lowercase();
26 ALIASES.iter().find(|(a, _)| *a == lower).map_or(lower, |(_, c)| (*c).to_string())
27}
28
29pub fn is_alias(ext: &str) -> bool {
31 let lower = ext.to_ascii_lowercase();
32 ALIASES.iter().any(|(a, _)| *a == lower)
33}
34
35pub struct Renamed {
37 _dir: tempfile::TempDir,
38 path: PathBuf,
39}
40
41impl Renamed {
42 pub fn path(&self) -> &Path {
43 &self.path
44 }
45}
46
47pub fn renamed(path: &Path) -> Option<Renamed> {
51 let ext = path.extension()?.to_str()?;
52 if !is_alias(ext) {
53 return None;
54 }
55 let dir = tempfile::Builder::new().prefix("falcon-alias-").tempdir().ok()?;
56 let target = dir.path().join(Path::new(path.file_stem()?).with_extension(canonical(ext)));
57 std::fs::hard_link(path, &target).or_else(|_| std::fs::copy(path, &target).map(|_| ())).ok()?;
58 Some(Renamed { _dir: dir, path: target })
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64
65 #[test]
66 fn aliases_map_to_their_canonical_extension() {
67 assert_eq!(canonical("JFIF"), "jpg");
68 assert_eq!(canonical("psdt"), "psd");
69 assert_eq!(canonical("m2ts"), "mts");
70 assert_eq!(canonical("PNG"), "png");
71 assert!(is_alias("Insp") && !is_alias("jpg"));
72 }
73
74 #[test]
75 fn a_renamed_file_has_the_canonical_name_and_the_same_bytes() {
76 let tmp = tempfile::tempdir().unwrap();
77 let src = tmp.path().join("shot.psdt");
78 std::fs::write(&src, b"8BPS").unwrap();
79 let r = renamed(&src).unwrap();
80 assert_eq!(r.path().file_name().unwrap(), "shot.psd");
81 assert_eq!(std::fs::read(r.path()).unwrap(), b"8BPS");
82 assert!(renamed(&tmp.path().join("a.png")).is_none());
83 }
84}