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}