Skip to main content

falcon_library/
alias.rs

1//! Extensions Eagle accepts as another extension's name for the same bytes
2//! (`.jfif` is a JPEG, `.psdt` a Photoshop template). macOS knows some of
3//! them by no type, so QuickLook refuses the name even though it reads the
4//! contents; falcon maps each to the extension that works and hands QuickLook
5//! the file under that name.
6
7use std::path::{Path, PathBuf};
8
9/// The extensions that stand for another, with the one they stand for.
10pub 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
22/// The extension `ext` (any case) stands for, or `ext` lower-cased when it
23/// is not an alias.
24pub 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
29/// Whether `ext` is one of the alias names.
30pub fn is_alias(ext: &str) -> bool {
31    let lower = ext.to_ascii_lowercase();
32    ALIASES.iter().any(|(a, _)| *a == lower)
33}
34
35/// A file under the name QuickLook needs, kept while it is alive.
36pub 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
47/// `path` under its canonical extension in a temporary folder (a hard link,
48/// else a copy), or `None` when its extension is not an alias or the link
49/// can't be made.
50pub 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}