use rustc_hash::FxHashMap;
#[derive(Debug, Clone)]
pub struct Entry {
pub rel: Box<str>,
pub root: u16,
pub is_dir: bool,
pub size: u64,
}
#[derive(Debug, Default)]
pub struct IndexStore {
entries: Vec<Entry>,
by_key: FxHashMap<(u16, Box<str>), u32>,
}
impl IndexStore {
pub fn new() -> Self {
Self::default()
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn entries(&self) -> &[Entry] {
&self.entries
}
pub fn insert(&mut self, root: u16, rel: Box<str>, is_dir: bool, size: u64) -> bool {
let key = (root, rel.clone());
if let Some(&idx) = self.by_key.get(&key) {
let e = &mut self.entries[idx as usize];
e.is_dir = is_dir;
e.size = size;
return false;
}
let idx = self.entries.len() as u32;
self.entries.push(Entry {
rel,
root,
is_dir,
size,
});
self.by_key.insert(key, idx);
true
}
pub fn remove(&mut self, root: u16, rel: &str) -> bool {
let key = (root, Box::<str>::from(rel));
let Some(idx) = self.by_key.remove(&key) else {
return false;
};
let idx = idx as usize;
self.entries.swap_remove(idx);
if idx < self.entries.len() {
let moved = &self.entries[idx];
let moved_key = (moved.root, moved.rel.clone());
self.by_key.insert(moved_key, idx as u32);
}
true
}
pub fn remove_tree(&mut self, root: u16, rel: &str) -> usize {
let prefix = format!("{rel}/");
let mut removed = 0usize;
let victims: Vec<Box<str>> = self
.entries
.iter()
.filter(|e| e.root == root && (&*e.rel == rel || e.rel.starts_with(&prefix)))
.map(|e| e.rel.clone())
.collect();
for rel in victims {
if self.remove(root, &rel) {
removed += 1;
}
}
removed
}
pub fn clear(&mut self) {
self.entries.clear();
self.by_key.clear();
}
pub fn browse(&self, root: u16, rel_dir: &str) -> Vec<&Entry> {
let prefix = if rel_dir.is_empty() {
String::new()
} else {
format!("{}/", rel_dir.trim_end_matches('/'))
};
let mut out: Vec<&Entry> = self
.entries
.iter()
.filter(|e| {
e.root == root && e.rel.starts_with(prefix.as_str()) && {
let rest = &e.rel[prefix.len()..];
!rest.is_empty() && !rest.contains('/')
}
})
.collect();
out.sort_by(|a, b| {
b.is_dir
.cmp(&a.is_dir)
.then_with(|| a.rel.to_ascii_lowercase().cmp(&b.rel.to_ascii_lowercase()))
});
out
}
pub fn iter_root(&self, root: u16) -> impl Iterator<Item = &Entry> {
self.entries.iter().filter(move |e| e.root == root)
}
}
#[cfg(test)]
#[path = "store_tests.rs"]
mod tests;