1use parking_lot::Mutex;
2
3use super::frn::FrnIndex;
4use super::{EncodedEntry, EntryId, Frn, NO_PARENT, RawEntry, SortKey, VolumeIndex};
5
6pub struct VolumeIndexBuilder {
9 idx: VolumeIndex,
10 parent_frns: Vec<Frn>,
11}
12
13#[derive(Debug, Default, Clone, Copy)]
15pub struct FinishTimings {
16 pub build_ms: u64,
18 pub sort_ms: u64,
20}
21
22impl VolumeIndexBuilder {
23 #[must_use]
26 pub fn new(volume_label: &str, root_record: u64) -> Self {
27 let mut idx = VolumeIndex {
28 lower_pool: Vec::new(),
29 orig_pool: Vec::new(),
30 orig_off: Vec::new(),
31 name_off: Vec::new(),
32 name_len: Vec::new(),
33 parent: Vec::new(),
34 size_lo: Vec::new(),
35 size_ovf: rustc_hash::FxHashMap::default(),
36 mtime: Vec::new(),
37 frn: Vec::new(),
38 flag: Vec::new(),
39 frn_index: FrnIndex::default(),
40 perm_name: Vec::new(),
41 content_generation: 0,
42 structural_generation: 0,
43 dir_topology_generation: 0,
44 tombstones: 0,
45 dead_name_bytes: 0,
46 derived_cache: Mutex::new(None),
47 };
48 let units: Vec<u16> = volume_label.encode_utf16().collect();
49 let root = idx.push_raw(
52 &RawEntry {
53 parent_frn: Frn(u64::MAX), frn: Frn(root_record),
55 name_utf16: &units,
56 is_dir: true,
57 is_reparse: false,
58 is_hidden: false,
59 is_system: false,
60 size: 0,
61 mtime: 0,
62 },
63 VolumeIndex::ROOT,
64 );
65 debug_assert_eq!(root, VolumeIndex::ROOT);
66 idx.parent[root as usize] = NO_PARENT;
67 Self {
68 idx,
69 parent_frns: vec![Frn(u64::MAX)],
70 }
71 }
72
73 pub fn push(&mut self, e: RawEntry) {
76 let id = self.idx.push_raw(&e, VolumeIndex::ROOT);
77 self.parent_frns.push(e.parent_frn);
78 debug_assert_eq!(self.parent_frns.len(), id as usize + 1);
79 }
80
81 pub fn push_encoded(&mut self, e: EncodedEntry) {
84 let id = self.idx.push_encoded(&e, VolumeIndex::ROOT);
85 self.parent_frns.push(e.parent_frn);
86 debug_assert_eq!(self.parent_frns.len(), id as usize + 1);
87 }
88
89 pub const fn len(&self) -> usize {
91 self.idx.len()
92 }
93
94 pub const fn is_empty(&self) -> bool {
97 self.idx.is_empty()
98 }
99
100 pub fn finish(self) -> VolumeIndex {
103 self.finish_timed().0
104 }
105
106 pub fn finish_timed(mut self) -> (VolumeIndex, FinishTimings) {
109 use rayon::prelude::*;
110
111 let mut timings = FinishTimings::default();
112 let t = std::time::Instant::now();
113
114 self.idx.frn_index = FrnIndex::build(&self.idx.frn, &self.idx.flag);
116
117 {
120 let VolumeIndex {
121 frn_index,
122 parent,
123 frn,
124 flag,
125 ..
126 } = &mut self.idx;
127 let parent_frns = &self.parent_frns;
128 parent
129 .par_iter_mut()
130 .enumerate()
131 .skip(1) .for_each(|(i, p)| {
133 *p = frn_index
134 .lookup(parent_frns[i].record(), frn, flag)
135 .unwrap_or(VolumeIndex::ROOT);
136 });
137 }
138
139 {
143 let n = self.idx.len();
144 let mut done = vec![false; n];
145 let root = VolumeIndex::ROOT as usize;
146 self.idx.recompute_excluded(VolumeIndex::ROOT);
147 done[root] = true;
148 let mut stack: Vec<EntryId> = Vec::new();
149 for start in 0..n as u32 {
150 let mut cur = start;
151 while !done[cur as usize] && stack.len() < 4096 {
152 stack.push(cur);
153 cur = self.idx.parent[cur as usize];
154 if cur == NO_PARENT {
155 break;
156 }
157 }
158 while let Some(id) = stack.pop() {
159 self.idx.recompute_excluded(id);
160 done[id as usize] = true;
161 }
162 }
163 }
164
165 timings.build_ms = t.elapsed().as_millis() as u64;
166 let t = std::time::Instant::now();
167
168 let mut perm_name: Vec<EntryId> = (0..self.idx.len() as u32).collect();
172 let idx = &self.idx;
173 perm_name.par_sort_unstable_by(|&a, &b| idx.cmp_by(SortKey::Name, a, b));
174 self.idx.perm_name = perm_name;
175 timings.sort_ms = t.elapsed().as_millis() as u64;
176 (self.idx, timings)
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183 use crate::index::testutil::{build_sample, raw, raw_attr, u16s};
184
185 #[test]
186 fn parents_resolve_across_scan_order() {
187 let idx = build_sample();
188 let note = idx.entry_by_record(100).unwrap();
189 let docs = idx.entry_by_record(50).unwrap();
190 assert_eq!(idx.parent(note), docs);
191 assert_eq!(idx.parent(docs), VolumeIndex::ROOT);
192 }
193
194 #[test]
195 fn orphan_attaches_to_root() {
196 let mut b = VolumeIndexBuilder::new("C:", 5);
197 let name = u16s("lost.txt");
198 b.push(raw(7, 999_999, &name, false, 1, 1));
199 let idx = b.finish();
200 let id = idx.entry_by_record(7).unwrap();
201 assert_eq!(idx.parent(id), VolumeIndex::ROOT);
202 }
203
204 #[test]
205 fn excluded_propagates_down_branches() {
206 let bin = u16s("$Recycle.Bin");
209 let sub = u16s("sub");
210 let ghost = u16s("ghost.txt");
211 let git = u16s(".git");
212 let normal = u16s("normal.txt");
213 let mut b = VolumeIndexBuilder::new("C:", 5);
214 b.push(raw_attr(30, 20, &ghost, false, false, false));
216 b.push(raw_attr(20, 10, &sub, true, false, false));
217 b.push(raw_attr(10, 5, &bin, true, false, true)); b.push(raw_attr(40, 5, &git, false, true, false)); b.push(raw_attr(50, 5, &normal, false, false, false));
220 let idx = b.finish();
221
222 for (rec, want) in [(10, true), (20, true), (30, true), (40, true), (50, false)] {
223 let id = idx.entry_by_record(rec).unwrap();
224 assert_eq!(idx.is_excluded(id), want, "record {rec}");
225 }
226 }
227}