1use std::any::Any;
2use std::sync::Arc;
3
4use parking_lot::Mutex;
5use rustc_hash::FxHashMap;
6
7use super::frn::FrnIndex;
8use super::{EntryId, Frn, NO_PARENT, RecordNo, SortKey, flags};
9
10pub struct VolumeIndex {
16 pub(super) lower_pool: Vec<u8>,
23 pub(super) orig_pool: Vec<u8>,
24 pub(super) orig_off: Vec<u32>,
25 pub(super) name_off: Vec<u32>,
26 pub(super) name_len: Vec<u16>,
27 pub(super) parent: Vec<EntryId>,
28 pub(super) size_lo: Vec<u32>,
32 pub(super) size_ovf: FxHashMap<EntryId, u64>,
33 pub(super) mtime: Vec<i64>,
34 pub(super) frn: Vec<u64>,
35 pub(super) flag: Vec<u8>,
36 pub(super) frn_index: FrnIndex,
37 pub(super) perm_name: Vec<EntryId>,
42 pub(super) content_generation: u64,
43 pub(super) structural_generation: u64,
44 pub(super) dir_topology_generation: u64,
49 pub(super) tombstones: u32,
50 pub(super) dead_name_bytes: u64,
56 pub(super) derived_cache: Mutex<Option<DerivedCache>>,
60}
61
62pub(super) type DerivedMap = FxHashMap<std::any::TypeId, Arc<dyn Any + Send + Sync>>;
63
64pub(super) struct DerivedCache {
69 generation: u64,
70 current: DerivedMap,
71 prev: DerivedMap,
72}
73
74#[inline]
83fn pool_lower_name<'a>(
84 lower_pool: &'a [u8],
85 name_off: &[u32],
86 name_len: &[u16],
87 id: EntryId,
88) -> &'a [u8] {
89 let off = name_off[id as usize] as usize;
90 &lower_pool[off..off + name_len[id as usize] as usize]
91}
92
93#[inline]
95fn column_size(size_lo: &[u32], size_ovf: &FxHashMap<EntryId, u64>, id: EntryId) -> u64 {
96 match size_lo[id as usize] {
97 u32::MAX => size_ovf[&id],
98 v => v as u64,
99 }
100}
101
102impl VolumeIndex {
103 pub const fn len(&self) -> usize {
105 self.name_off.len()
106 }
107
108 pub const fn is_empty(&self) -> bool {
110 self.len() == 0
111 }
112
113 pub const fn live_len(&self) -> usize {
115 self.len() - self.tombstones as usize
116 }
117
118 pub const ROOT: EntryId = 0;
120
121 #[inline]
124 pub fn name(&self, id: EntryId) -> &[u8] {
125 match self.orig_off[id as usize] {
126 u32::MAX => self.lower_name(id),
127 off => {
128 &self.orig_pool[off as usize..off as usize + self.name_len[id as usize] as usize]
129 }
130 }
131 }
132
133 #[inline]
136 pub fn lower_name(&self, id: EntryId) -> &[u8] {
137 pool_lower_name(&self.lower_pool, &self.name_off, &self.name_len, id)
138 }
139
140 #[inline]
142 pub fn is_live(&self, id: EntryId) -> bool {
143 self.flag[id as usize] & flags::TOMBSTONE == 0
144 }
145
146 #[inline]
148 pub fn is_excluded(&self, id: EntryId) -> bool {
149 self.flag[id as usize] & flags::EXCLUDED != 0
150 }
151
152 #[inline]
154 pub fn is_dir(&self, id: EntryId) -> bool {
155 self.flag[id as usize] & flags::IS_DIR != 0
156 }
157
158 #[inline]
160 pub fn is_reparse(&self, id: EntryId) -> bool {
161 self.flag[id as usize] & flags::REPARSE != 0
162 }
163
164 #[inline]
167 pub fn size(&self, id: EntryId) -> u64 {
168 column_size(&self.size_lo, &self.size_ovf, id)
169 }
170
171 pub(super) fn set_size(&mut self, id: EntryId, v: u64) {
175 if v >= u32::MAX as u64 {
176 self.size_lo[id as usize] = u32::MAX;
177 self.size_ovf.insert(id, v);
178 } else {
179 self.size_lo[id as usize] = v as u32;
180 self.size_ovf.remove(&id);
181 }
182 }
183
184 pub(super) fn push_size(&mut self, v: u64) {
186 if v >= u32::MAX as u64 {
187 let id = self.size_lo.len() as EntryId;
188 self.size_lo.push(u32::MAX);
189 self.size_ovf.insert(id, v);
190 } else {
191 self.size_lo.push(v as u32);
192 }
193 }
194
195 #[inline]
197 pub fn mtime(&self, id: EntryId) -> i64 {
198 self.mtime[id as usize]
199 }
200
201 #[inline]
203 pub fn parent(&self, id: EntryId) -> EntryId {
204 self.parent[id as usize]
205 }
206
207 #[inline]
209 pub fn frn(&self, id: EntryId) -> Frn {
210 Frn(self.frn[id as usize])
211 }
212
213 pub fn entry_by_record(&self, record: impl Into<RecordNo>) -> Option<EntryId> {
217 self.frn_index.lookup(record.into(), &self.frn, &self.flag)
218 }
219
220 #[inline]
222 pub(crate) fn name_off_of(&self, id: EntryId) -> u32 {
223 self.name_off[id as usize]
224 }
225
226 #[inline]
227 pub(crate) fn name_len_of(&self, id: EntryId) -> usize {
228 self.name_len[id as usize] as usize
229 }
230
231 #[inline]
236 pub(crate) fn is_fold_identical(&self, id: EntryId) -> bool {
237 self.orig_off[id as usize] == u32::MAX
238 }
239
240 #[inline]
241 pub(crate) fn lower_pool_bytes(&self) -> &[u8] {
242 &self.lower_pool
243 }
244
245 pub const fn content_generation(&self) -> u64 {
248 self.content_generation
249 }
250
251 pub const fn structural_generation(&self) -> u64 {
254 self.structural_generation
255 }
256
257 pub(crate) const fn dir_topology_generation(&self) -> u64 {
258 self.dir_topology_generation
259 }
260
261 pub(crate) const fn bump_structural_from(&mut self, prev: u64) {
266 self.structural_generation = prev + 1;
267 }
268
269 pub(crate) fn cached_derived<T, F>(&self, build: F) -> Arc<T>
273 where
274 T: Any + Send + Sync,
275 F: FnOnce() -> T,
276 {
277 self.with_derived(|_| build())
278 }
279
280 pub(crate) fn cached_derived_or_update<T, F>(&self, build: F) -> Arc<T>
284 where
285 T: Any + Send + Sync,
286 F: FnOnce(Option<Arc<T>>) -> T,
287 {
288 self.with_derived(build)
289 }
290
291 pub(crate) fn derived_probe<T: Any + Send + Sync>(&self) -> Option<Arc<T>> {
294 let guard = self.derived_cache.lock();
295 let cache = guard.as_ref()?;
296 if cache.generation != self.content_generation {
297 return None;
298 }
299 cache
300 .current
301 .get(&std::any::TypeId::of::<T>())?
302 .clone()
303 .downcast::<T>()
304 .ok()
305 }
306
307 fn with_derived<T, F>(&self, build: F) -> Arc<T>
308 where
309 T: Any + Send + Sync,
310 F: FnOnce(Option<Arc<T>>) -> T,
311 {
312 let key = std::any::TypeId::of::<T>();
313 let mut guard = self.derived_cache.lock();
314 let cache = guard.get_or_insert_with(|| DerivedCache {
315 generation: self.content_generation,
316 current: DerivedMap::default(),
317 prev: DerivedMap::default(),
318 });
319 if cache.generation != self.content_generation {
320 cache.prev = std::mem::take(&mut cache.current);
321 cache.generation = self.content_generation;
322 }
323 if let Some(v) = cache.current.get(&key)
324 && let Ok(t) = v.clone().downcast::<T>()
325 {
326 return t;
327 }
328 let previous = cache.prev.remove(&key).and_then(|v| v.downcast::<T>().ok());
329 let t = Arc::new(build(previous));
330 cache.current.insert(key, t.clone());
331 t
332 }
333
334 pub fn stats(&self, volume: &str) -> crate::metrics::IndexStats {
337 let n = self.len() as u64;
338 let offsets = (self.name_off.capacity() * 4
339 + self.name_len.capacity() * 2
340 + self.orig_off.capacity() * 4) as u64;
341 let perms = (self.perm_name.capacity() * 4) as u64;
344 let frn_map = self.frn_index.bytes();
347 let mut s = crate::metrics::IndexStats {
348 volume: volume.to_string(),
349 entries: n,
350 live_entries: self.live_len() as u64,
351 tombstones: self.tombstones as u64,
352 name_pool_bytes: self.orig_pool.capacity() as u64,
356 lower_pool_bytes: self.lower_pool.capacity() as u64,
357 offsets_bytes: offsets,
358 parent_bytes: (self.parent.capacity() * 4) as u64,
359 size_bytes: (self.size_lo.capacity() * 4
362 + self.size_ovf.capacity() * (std::mem::size_of::<(EntryId, u64)>() + 1))
363 as u64,
364 mtime_bytes: (self.mtime.capacity() * 8) as u64,
365 frn_bytes: (self.frn.capacity() * 8) as u64,
366 flag_bytes: self.flag.capacity() as u64,
367 permutations_bytes: perms,
368 frn_map_bytes: frn_map,
369 dead_name_bytes: self.dead_name_bytes,
370 content_generation: self.content_generation,
371 structural_generation: self.structural_generation,
372 ..Default::default()
373 };
374 let pool_bytes = s.name_pool_bytes + s.lower_pool_bytes;
377 s.pool_garbage_ratio = if pool_bytes > 0 {
378 self.dead_name_bytes as f64 / pool_bytes as f64
379 } else {
380 0.0
381 };
382 s.total_bytes = s.name_pool_bytes
383 + s.lower_pool_bytes
384 + s.offsets_bytes
385 + s.parent_bytes
386 + s.size_bytes
387 + s.mtime_bytes
388 + s.frn_bytes
389 + s.flag_bytes
390 + s.permutations_bytes
391 + s.frn_map_bytes;
392 s.bytes_per_entry = if n > 0 {
393 s.total_bytes as f64 / n as f64
394 } else {
395 0.0
396 };
397 s
398 }
399
400 pub fn shrink_to_fit(&mut self) {
402 self.frn_index.shrink_to_fit();
403 self.lower_pool.shrink_to_fit();
404 self.orig_pool.shrink_to_fit();
405 self.orig_off.shrink_to_fit();
406 self.name_off.shrink_to_fit();
407 self.name_len.shrink_to_fit();
408 self.parent.shrink_to_fit();
409 self.size_lo.shrink_to_fit();
410 self.size_ovf.shrink_to_fit();
411 self.mtime.shrink_to_fit();
412 self.frn.shrink_to_fit();
413 self.flag.shrink_to_fit();
414 self.perm_name.shrink_to_fit();
415 }
416
417 pub fn name_permutation(&self) -> &[EntryId] {
420 &self.perm_name
421 }
422
423 pub fn append_path(&self, id: EntryId, out: &mut Vec<u8>) {
426 self.append_parent_path(id, out);
427 if id != Self::ROOT {
428 out.extend_from_slice(self.name(id));
429 }
430 }
431
432 pub fn append_parent_path(&self, id: EntryId, out: &mut Vec<u8>) {
434 let mut chain = [0u32; 128];
435 let mut depth = 0;
436 let mut cur = if id == Self::ROOT {
437 NO_PARENT
438 } else {
439 self.parent(id)
440 };
441 while cur != NO_PARENT && depth < chain.len() {
442 chain[depth] = cur;
443 depth += 1;
444 cur = if cur == Self::ROOT {
445 NO_PARENT
446 } else {
447 self.parent(cur)
448 };
449 }
450 for &c in chain[..depth].iter().rev() {
451 let name = self.name(c);
456 if !name.is_empty() {
457 out.extend_from_slice(name);
458 out.push(b'\\');
459 }
460 }
461 }
462
463 pub fn tombstone_ratio(&self) -> f64 {
466 if self.is_empty() {
467 0.0
468 } else {
469 self.tombstones as f64 / self.len() as f64
470 }
471 }
472
473 #[inline]
477 pub(crate) fn cmp_by(&self, key: SortKey, a: EntryId, b: EntryId) -> std::cmp::Ordering {
478 self.sort_columns().cmp_by(key, a, b)
479 }
480
481 pub(super) fn sort_columns(&self) -> SortColumns<'_> {
482 SortColumns {
483 lower_pool: &self.lower_pool,
484 name_off: &self.name_off,
485 name_len: &self.name_len,
486 size_lo: &self.size_lo,
487 size_ovf: &self.size_ovf,
488 mtime: &self.mtime,
489 }
490 }
491}
492
493pub(super) struct SortColumns<'a> {
498 lower_pool: &'a [u8],
499 name_off: &'a [u32],
500 name_len: &'a [u16],
501 size_lo: &'a [u32],
502 size_ovf: &'a FxHashMap<EntryId, u64>,
503 mtime: &'a [i64],
504}
505
506impl<'a> SortColumns<'a> {
507 pub(super) const fn new(
508 lower_pool: &'a [u8],
509 name_off: &'a [u32],
510 name_len: &'a [u16],
511 size_lo: &'a [u32],
512 size_ovf: &'a FxHashMap<EntryId, u64>,
513 mtime: &'a [i64],
514 ) -> Self {
515 Self {
516 lower_pool,
517 name_off,
518 name_len,
519 size_lo,
520 size_ovf,
521 mtime,
522 }
523 }
524
525 #[inline]
526 fn lower_name(&self, id: EntryId) -> &[u8] {
527 pool_lower_name(self.lower_pool, self.name_off, self.name_len, id)
528 }
529
530 #[inline]
531 fn size_of(&self, id: EntryId) -> u64 {
532 column_size(self.size_lo, self.size_ovf, id)
533 }
534
535 #[inline]
538 pub(super) fn cmp_by(&self, key: SortKey, a: EntryId, b: EntryId) -> std::cmp::Ordering {
539 match key {
540 SortKey::Name => self.lower_name(a).cmp(self.lower_name(b)).then(a.cmp(&b)),
541 SortKey::Size => self.size_of(a).cmp(&self.size_of(b)).then(a.cmp(&b)),
542 SortKey::Mtime => self.mtime[a as usize]
543 .cmp(&self.mtime[b as usize])
544 .then(a.cmp(&b)),
545 }
546 }
547}
548
549#[cfg(test)]
550mod tests {
551
552 use crate::index::testutil::build_sample;
553
554 #[test]
555 fn full_path_builds_lazily() {
556 let idx = build_sample();
557 let note = idx.entry_by_record(100).unwrap();
558 let mut p = Vec::new();
559 idx.append_path(note, &mut p);
560 assert_eq!(p, b"C:\\docs\\Note.TXT");
561
562 let mut pp = Vec::new();
563 idx.append_parent_path(note, &mut pp);
564 assert_eq!(pp, b"C:\\docs\\");
565 }
566
567 #[test]
568 fn name_permutation_is_sorted() {
569 let idx = build_sample();
570 let by_name: Vec<&[u8]> = idx
571 .name_permutation()
572 .iter()
573 .map(|&id| idx.lower_name(id))
574 .collect();
575 let mut expect = by_name.clone();
576 expect.sort();
577 assert_eq!(by_name, expect);
578 }
579}