1use std::{
2 collections::HashMap,
3 fs::File,
4 io::{BufRead, BufReader, BufWriter, Write},
5 path::PathBuf,
6 time::SystemTime,
7};
8
9pub const DEFAULT_INDEX_FILE_NAME: &str = ".tiny-dc";
10
11#[derive(Debug)]
12pub struct DirectoryIndexEntry {
13 rank: f64,
15 last_accessed: u64,
17}
18
19impl DirectoryIndexEntry {
20 fn new() -> Self {
21 DirectoryIndexEntry {
22 rank: 0.0,
23 last_accessed: SystemTime::now()
24 .duration_since(SystemTime::UNIX_EPOCH)
25 .unwrap()
26 .as_secs(),
27 }
28 }
29
30 fn update(&mut self) {
31 let now = SystemTime::now()
32 .duration_since(SystemTime::UNIX_EPOCH)
33 .unwrap()
34 .as_secs();
35
36 self.last_accessed = now;
37
38 self.rank = (self.rank * 0.99) + 1.0;
42 }
43
44 fn frecent_score(&self) -> f64 {
45 let now = SystemTime::now()
46 .duration_since(SystemTime::UNIX_EPOCH)
47 .unwrap()
48 .as_secs();
49
50 let dx = now - self.last_accessed;
52
53 10000.0 * self.rank * (3.75 / ((0.0001 * dx as f64 + 1.0) + 0.25))
63 }
64}
65
66#[derive(Debug, Default)]
70pub struct DirectoryIndex {
71 path: PathBuf,
72 data: HashMap<PathBuf, DirectoryIndexEntry>,
73}
74
75impl DirectoryIndex {
76 pub fn try_from(path: PathBuf) -> anyhow::Result<Self> {
78 let file = if path.exists() {
79 File::open(&path)?
81 } else {
82 File::create_new(&path)?
84 };
85
86 let reader = BufReader::new(file);
87 let mut data = HashMap::new();
88
89 for line in reader.lines() {
90 let line = line?;
91 let parts: Vec<&str> = line.split('|').collect();
92
93 if parts.len() != 3 {
94 continue;
96 }
97
98 let path = PathBuf::from(parts[0]);
99 let rank: f64 = parts[1].parse().unwrap_or(0.0);
100 let last_accessed: u64 = parts[2].parse().unwrap_or(0);
101
102 let entry = DirectoryIndexEntry {
103 last_accessed,
104 rank,
105 };
106 data.insert(path.clone(), entry);
107 }
108
109 Ok(DirectoryIndex { path, data })
110 }
111
112 fn save_to_disk(&self) -> anyhow::Result<()> {
113 let file = File::create(self.path.clone())?;
115 let mut writer = BufWriter::new(file);
116
117 for (path, entry) in &self.data {
118 writeln!(
119 writer,
120 "{}|{}|{}",
121 path.display(),
122 entry.rank,
123 entry.last_accessed
124 )?;
125 }
126
127 Ok(())
128 }
129
130 pub fn push(&mut self, path: PathBuf) -> anyhow::Result<()> {
134 if !path.exists() {
135 return Ok(());
137 }
138
139 if let Some(entry) = self.data.get_mut(&path) {
140 entry.update();
142 } else {
143 let entry = DirectoryIndexEntry::new();
144 self.data.insert(path, entry);
145 }
146
147 self.save_to_disk()?;
148
149 Ok(())
150 }
151
152 pub fn z(&mut self, query: &str) -> anyhow::Result<Option<PathBuf>> {
160 let mut matches = Vec::new();
161 let query_lower = query.to_lowercase();
162
163 for (path, stats) in &self.data {
164 let path_str = path.to_string_lossy();
165 let frecent_score = stats.frecent_score();
166
167 if path_str.contains(query) {
168 matches.push((path.clone(), frecent_score, 0));
170 } else if path_str.to_lowercase().contains(&query_lower) {
171 matches.push((path.clone(), frecent_score, 1));
173 }
174 }
175
176 if matches.is_empty() {
177 return Ok(None);
178 }
179
180 if let Some((ancestor, _, _)) = matches.iter().find(|(candidate, _, _)| {
182 matches
183 .iter()
184 .all(|(other, _, _)| other.starts_with(candidate))
185 }) {
186 return Ok(Some(ancestor.clone()));
187 }
188
189 matches.sort_by(|a, b| {
192 a.2.cmp(&b.2)
193 .then(b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal))
194 .then(a.0.components().count().cmp(&b.0.components().count()))
195 });
196
197 let mut is_index_updated = false;
198 let mut result = None;
199
200 for (path, _, _) in matches.iter() {
201 if path.exists() {
202 result = Some(path.clone());
204 break;
205 }
206
207 self.data.remove(path);
209 is_index_updated = true;
210 }
211
212 if is_index_updated {
213 self.save_to_disk()?;
215 }
216
217 Ok(result)
218 }
219
220 pub fn get_all_entries_ordered_by_rank(&self) -> Vec<PathBuf> {
222 let mut entries: Vec<_> = self.data.iter().collect();
223 entries.sort_by(|a, b| {
224 b.1.frecent_score()
225 .partial_cmp(&a.1.frecent_score())
226 .unwrap_or(std::cmp::Ordering::Equal)
227 });
228 entries.into_iter().map(|(path, _)| path.clone()).collect()
229 }
230}