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, queries: Vec<String>) -> anyhow::Result<Option<PathBuf>> {
160 let mut matches = Vec::new();
161
162 for (path, stats) in &self.data {
163 let path_str = path.to_string_lossy();
164 let frecent_score = stats.frecent_score();
165
166 if queries.iter().all(|q| path_str.contains(q)) {
167 matches.push((path.clone(), frecent_score, 0))
169 } else if queries.iter().all(|q| path_str.to_lowercase().contains(q)) {
170 matches.push((path.clone(), frecent_score, 1));
172 }
173 }
174
175 if matches.is_empty() {
176 return Ok(None);
177 }
178
179 if let Some((ancestor, _, _)) = matches.iter().find(|(candidate, _, _)| {
181 matches
182 .iter()
183 .all(|(other, _, _)| other.starts_with(candidate))
184 }) {
185 return Ok(Some(ancestor.clone()));
186 }
187
188 matches.sort_by(|a, b| {
191 a.2.cmp(&b.2)
192 .then(b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal))
193 .then(a.0.components().count().cmp(&b.0.components().count()))
194 });
195
196 let mut is_index_updated = false;
197 let mut result = None;
198
199 for (path, _, _) in matches.iter() {
200 if path.exists() {
201 result = Some(path.clone());
203 break;
204 }
205
206 self.data.remove(path);
208 is_index_updated = true;
209 }
210
211 if is_index_updated {
212 self.save_to_disk()?;
214 }
215
216 Ok(result)
217 }
218
219 pub fn get_all_entries_ordered_by_rank(&self) -> Vec<PathBuf> {
221 let mut entries: Vec<_> = self.data.iter().collect();
222 entries.sort_by(|a, b| {
223 b.1.frecent_score()
224 .partial_cmp(&a.1.frecent_score())
225 .unwrap_or(std::cmp::Ordering::Equal)
226 });
227 entries.into_iter().map(|(path, _)| path.clone()).collect()
228 }
229}