freedesktop_desktop_entry/
iter.rs1use std::{
5 collections::{BTreeSet, VecDeque},
6 fs,
7 path::PathBuf,
8};
9
10use crate::DesktopEntry;
11
12pub struct Iter {
13 directories_to_walk: VecDeque<PathBuf>,
14 actively_walking: Option<VecDeque<PathBuf>>,
15 visited: BTreeSet<PathBuf>,
16}
17
18impl Iter {
19 #[inline]
21 pub fn new<I: Iterator<Item = PathBuf>>(directories_to_walk: I) -> Self {
22 Self {
23 directories_to_walk: directories_to_walk.collect(),
24 actively_walking: None,
25 visited: BTreeSet::default(),
26 }
27 }
28}
29
30impl Iterator for Iter {
31 type Item = PathBuf;
32
33 fn next(&mut self) -> Option<Self::Item> {
34 'outer: loop {
35 let mut paths = match self.actively_walking.take() {
36 Some(dir) => dir,
37 None => {
38 while let Some(path) = self.directories_to_walk.pop_front() {
39 match fs::read_dir(&path) {
40 Ok(dir) if self.visited.insert(path.clone()) => {
41 self.actively_walking = Some({
42 let mut entries = dir
44 .filter_map(Result::ok)
45 .map(|entry| entry.path())
46 .collect::<VecDeque<_>>();
47 entries.make_contiguous().sort_unstable();
48 entries
49 });
50
51 continue 'outer;
52 }
53
54 _ => continue,
57 }
58 }
59
60 return None;
61 }
62 };
63
64 'inner: while let Some(mut path) = paths.pop_front() {
65 path = match path.canonicalize() {
66 Ok(canonicalized) => canonicalized,
67 Err(_) => continue 'inner,
68 };
69
70 if let Ok(metadata) = path.metadata() {
71 if metadata.is_dir() {
72 if self.visited.insert(path.clone()) {
74 self.directories_to_walk.push_front(path);
75 }
76 } else if metadata.is_file()
77 && path.extension().is_some_and(|ext| ext == "desktop")
78 {
79 self.actively_walking = Some(paths);
80 return Some(path);
81 }
82 }
83 }
84 }
85 }
86}
87
88impl Iter {
89 #[inline]
90 pub fn entries<'i, 'l: 'i, L>(
91 self,
92 locales_filter: Option<&'l [L]>,
93 ) -> impl Iterator<Item = DesktopEntry> + 'i
94 where
95 L: AsRef<str>,
96 {
97 self.map(move |path| DesktopEntry::from_path(path, locales_filter))
98 .filter_map(|e| e.ok())
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use std::{fs, os::unix};
105
106 use super::{DesktopEntry, Iter};
107
108 #[test]
109 fn iter_yields_all_entries() {
110 let temp = tempfile::tempdir().unwrap();
111 let root = temp.path();
112
113 let dir_a = root.join("a");
116 let dir_a_a = dir_a.join("aa");
117 fs::create_dir_all(&dir_a_a).unwrap();
118 let file_a = dir_a.join("a.desktop");
119 let file_b = dir_a.join("b.desktop");
120 let file_c = dir_a_a.join("c.desktop");
121
122 let dir_b_bb_bbb = root.join("b/bb/bbb");
124 fs::create_dir_all(&dir_b_bb_bbb).unwrap();
125 let file_d = dir_b_bb_bbb.join("d.desktop");
126
127 let file_e = root.join("e.desktop");
129
130 let all_files = [file_a, file_b, file_c, file_d, file_e];
132 for file in &all_files {
133 let (name, _) = file
134 .file_name()
135 .unwrap()
136 .to_str()
137 .unwrap()
138 .split_once('.')
139 .unwrap();
140 fs::write(file, DesktopEntry::from_appid(name.to_string()).to_string()).unwrap();
141 }
142
143 let mut iter = Iter::new(
144 fs::read_dir(root)
145 .unwrap()
146 .map(|entry| entry.unwrap().path()),
147 );
148 for (expected, actual) in all_files.iter().zip(&mut iter) {
149 assert_eq!(*expected, actual);
150 }
151
152 assert_eq!(None, iter.next());
153 }
154
155 #[test]
156 fn iter_no_infinite_loop() {
157 let temp = tempfile::tempdir().unwrap();
159 let root = temp.path();
160 let dir = root.join("loop");
161 unix::fs::symlink(root, &dir).expect("Linking {dir:?} to {root:?}");
162
163 assert_eq!(
165 fs::canonicalize(root).unwrap(),
166 fs::canonicalize(&dir).unwrap(),
167 "Expected a loop where {dir:?} points to {root:?}"
168 );
169
170 let entry = DesktopEntry::from_appid("joshfakeapp123".into());
172 let entry_path = root.join("joshfakeapp123.desktop");
173 fs::write(&entry_path, entry.to_string()).expect("Writing entry: {entry_path:?}");
174
175 for (i, de) in Iter::new(
177 fs::read_dir(root)
178 .unwrap()
179 .map(|entry| entry.unwrap().path()),
180 )
181 .entries(Option::<&[&str]>::None)
182 .enumerate()
183 {
184 assert_eq!(entry.appid, de.appid);
185 if i > 0 {
186 panic!("Infinite loop");
187 }
188 }
189 }
190}