Skip to main content

freedesktop_sound/
lib.rs

1mod cache;
2mod theme;
3
4use cache::{CacheEntry, CACHE};
5use std::{io::BufRead, path::PathBuf};
6use theme::THEMES;
7
8pub fn list_themes() -> Vec<String> {
9    let mut themes = THEMES
10        .values()
11        .flatten()
12        .map(|path| &path.index)
13        .filter_map(|index| {
14            let file = std::fs::File::open(index).ok()?;
15            let mut reader = std::io::BufReader::new(file);
16
17            let mut line = String::new();
18            while let Ok(read) = reader.read_line(&mut line) {
19                if read == 0 {
20                    break;
21                }
22
23                if let Some(name) = line.strip_prefix("Name=") {
24                    return Some(name.trim().to_owned());
25                }
26
27                line.clear();
28            }
29
30            None
31        })
32        .collect::<Vec<_>>();
33    themes.dedup();
34    themes
35}
36
37pub struct LookupBuilder<'a> {
38    name: &'a str,
39    theme: &'a str,
40    cache: bool,
41}
42
43impl<'a> LookupBuilder<'a> {
44    fn new<'b: 'a>(name: &'b str) -> Self {
45        Self {
46            name,
47            theme: "freedesktop",
48            cache: false,
49        }
50    }
51
52    pub fn with_theme<'b: 'a>(mut self, theme: &'b str) -> Self {
53        self.theme = theme;
54        self
55    }
56
57    pub fn with_cache<'b: 'a>(mut self) -> Self {
58        self.cache = true;
59        self
60    }
61
62    pub fn find(self) -> Option<PathBuf> {
63        self.lookup_in_theme()
64    }
65
66    fn lookup_in_theme(&self) -> Option<PathBuf> {
67        if self.cache {
68            if let CacheEntry::Found(sound) = self.cache_lookup(self.theme) {
69                return Some(sound);
70            }
71        }
72
73        THEMES
74            .get(self.theme)
75            .or_else(|| THEMES.get("freedesktop"))
76            .and_then(|sound_themes| {
77                let sound = sound_themes
78                    .iter()
79                    .find_map(|theme| theme.try_get_sound(self.name))
80                    .or_else(|| {
81                        let mut parents = sound_themes
82                            .iter()
83                            .flat_map(|t| {
84                                let file = std::fs::read_to_string(&t.index).unwrap_or_default();
85                                t.inherits(file.as_ref())
86                                    .into_iter()
87                                    .map(String::from)
88                                    .collect::<Vec<String>>()
89                            })
90                            .collect::<Vec<_>>();
91                        parents.dedup();
92                        parents.into_iter().find_map(|parent| {
93                            THEMES.get(&parent).and_then(|parent| {
94                                parent.iter().find_map(|t| t.try_get_sound(self.name))
95                            })
96                        })
97                    })
98                    .or_else(|| {
99                        THEMES.get("freedesktop").and_then(|sound_themes| {
100                            sound_themes
101                                .iter()
102                                .find_map(|theme| theme.try_get_sound(self.name))
103                        })
104                    });
105
106                if self.cache {
107                    self.store(self.theme, sound)
108                } else {
109                    sound
110                }
111            })
112    }
113
114    #[inline]
115    fn cache_lookup(&self, theme: &str) -> CacheEntry {
116        CACHE.get(theme, self.name)
117    }
118
119    #[inline]
120    fn store(&self, theme: &str, sound: Option<PathBuf>) -> Option<PathBuf> {
121        CACHE.insert(theme, self.name, &sound);
122        sound
123    }
124}
125
126pub fn lookup(name: &str) -> LookupBuilder {
127    LookupBuilder::new(name)
128}
129
130#[cfg(test)]
131mod tests {
132    use std::path::PathBuf;
133
134    use crate::lookup;
135
136    #[test]
137    fn simple_lookup() {
138        let bell = lookup("bell").find();
139        let path = match PathBuf::from("/etc/NIXOS").exists() {
140            true => {
141                PathBuf::from("/run/current-system/sw/share/sounds/freedesktop/stereo/bell.oga")
142            }
143            false => PathBuf::from("/usr/share/sounds/freedesktop/stereo/bell.oga"),
144        };
145
146        assert!(bell.is_some_and(|b| b == path));
147    }
148}