use std::collections::HashSet;
use std::path::{Path, PathBuf};
use super::config::CorpusKind;
use super::error::IndexError;
use super::kinds::Index;
use super::snapshot::Snapshot;
use super::store;
pub struct Indexes {
snapshot: Snapshot,
}
impl Indexes {
#[must_use]
pub fn from_single(index: Index, root: PathBuf) -> Self {
Self {
snapshot: Snapshot::from_indexes(root, vec![index]),
}
}
pub fn open(sift_dir: &Path) -> Result<Self, IndexError> {
let store = store::IndexStore::open(sift_dir).map_err(|e| match e {
crate::Error::Index(ie) => ie,
crate::Error::Io(io) => IndexError::Io {
path: sift_dir.to_path_buf(),
source: io,
},
_ => IndexError::Io {
path: sift_dir.to_path_buf(),
source: std::io::Error::other(e.to_string()),
},
})?;
let snapshot = store.open_current().map_err(|e| match e {
crate::Error::Index(ie) => ie,
crate::Error::Io(io) => IndexError::Io {
path: sift_dir.to_path_buf(),
source: io,
},
_ => IndexError::Io {
path: sift_dir.to_path_buf(),
source: std::io::Error::other(e.to_string()),
},
})?;
Ok(Self { snapshot })
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.snapshot.is_empty()
}
#[must_use]
pub fn root(&self) -> &Path {
self.snapshot.root()
}
#[must_use]
pub fn candidates(&self, query: &crate::query::QuerySpec<'_>) -> Option<Vec<crate::Candidate>> {
let indexes = self.snapshot.indexes();
match indexes.len() {
0 => None,
1 => indexes[0].candidates(query),
_ => Self::candidates_multi(indexes, query),
}
}
#[must_use]
pub fn indexed_rel_paths(&self) -> HashSet<PathBuf> {
let indexes = self.snapshot.indexes();
let Some(first) = indexes.first() else {
return HashSet::new();
};
let mut paths: HashSet<PathBuf> = first
.all_files()
.into_iter()
.map(|c| c.rel_path().to_path_buf())
.collect();
for index in indexes.iter().skip(1) {
let next: HashSet<PathBuf> = index
.all_files()
.into_iter()
.map(|c| c.rel_path().to_path_buf())
.collect();
paths.retain(|p| next.contains(p));
if paths.is_empty() {
break;
}
}
paths
}
#[must_use]
pub fn unindexed_hits(&self, hits: impl IntoIterator<Item = PathBuf>) -> Vec<PathBuf> {
let indexed = self.indexed_rel_paths();
hits.into_iter()
.filter(|path| !indexed.contains(path))
.collect()
}
#[must_use]
pub(crate) fn complete_candidates(&self) -> Vec<crate::Candidate> {
let indexes = self.snapshot.indexes();
let mut iter = indexes.iter();
let Some(first) = iter.next() else {
return Vec::new();
};
let mut files = first.all_files();
for index in iter {
let next: HashSet<PathBuf> = index
.all_files()
.into_iter()
.map(|c| c.rel_path().to_path_buf())
.collect();
files.retain(|c| next.contains(c.rel_path()));
if files.is_empty() {
break;
}
}
files
}
fn candidates_multi(
indexes: &[Index],
query: &crate::query::QuerySpec<'_>,
) -> Option<Vec<crate::Candidate>> {
use rayon::prelude::*;
let sets: Vec<Vec<crate::Candidate>> = indexes
.par_iter()
.filter_map(|idx| idx.candidates(query))
.collect();
if sets.is_empty() {
return None;
}
let mut result = sets.into_iter();
let mut current = result.next()?;
for next in result {
let lookup: HashSet<&Path> = next.iter().map(crate::Candidate::rel_path).collect();
current.retain(|c| lookup.contains(c.rel_path()));
if current.is_empty() {
break;
}
}
Some(current)
}
#[must_use]
pub fn first(&self) -> Option<&Index> {
self.snapshot.indexes().first()
}
#[must_use]
pub fn corpus_kind(&self) -> Option<CorpusKind> {
let indexes = self.snapshot.indexes();
let kind = indexes.first()?.corpus_kind();
if indexes.iter().any(|idx| idx.corpus_kind() != kind) {
return None;
}
Some(kind)
}
}