sift_core/index/
registry.rs1use std::collections::HashSet;
2use std::path::{Path, PathBuf};
3
4use super::config::CorpusKind;
5use super::error::IndexError;
6use super::kinds::Index;
7use super::snapshot::Snapshot;
8use super::store;
9
10pub struct Indexes {
15 snapshot: Snapshot,
16}
17
18impl Indexes {
19 #[must_use]
23 pub fn from_single(index: Index, root: PathBuf) -> Self {
24 Self {
25 snapshot: Snapshot::from_indexes(root, vec![index]),
26 }
27 }
28
29 pub fn open(sift_dir: &Path) -> Result<Self, IndexError> {
38 let store = store::IndexStore::open(sift_dir).map_err(|e| match e {
39 crate::Error::Index(ie) => ie,
40 crate::Error::Io(io) => IndexError::Io {
41 path: sift_dir.to_path_buf(),
42 source: io,
43 },
44 _ => IndexError::Io {
45 path: sift_dir.to_path_buf(),
46 source: std::io::Error::other(e.to_string()),
47 },
48 })?;
49
50 let snapshot = store.open_current().map_err(|e| match e {
51 crate::Error::Index(ie) => ie,
52 crate::Error::Io(io) => IndexError::Io {
53 path: sift_dir.to_path_buf(),
54 source: io,
55 },
56 _ => IndexError::Io {
57 path: sift_dir.to_path_buf(),
58 source: std::io::Error::other(e.to_string()),
59 },
60 })?;
61
62 Ok(Self { snapshot })
63 }
64
65 #[must_use]
66 pub const fn is_empty(&self) -> bool {
67 self.snapshot.is_empty()
68 }
69
70 #[must_use]
71 pub fn root(&self) -> &Path {
72 self.snapshot.root()
73 }
74
75 #[must_use]
80 pub fn candidates(&self, query: &crate::query::QuerySpec<'_>) -> Option<Vec<crate::Candidate>> {
81 let indexes = self.snapshot.indexes();
82 match indexes.len() {
83 0 => None,
84 1 => indexes[0].candidates(query),
85 _ => Self::candidates_multi(indexes, query),
86 }
87 }
88
89 #[must_use]
91 pub(crate) fn complete_candidates(&self) -> Vec<crate::Candidate> {
92 let indexes = self.snapshot.indexes();
93 let mut iter = indexes.iter();
94 let Some(first) = iter.next() else {
95 return Vec::new();
96 };
97
98 let mut files = first.all_files();
99
100 for index in iter {
101 let next: HashSet<PathBuf> = index
102 .all_files()
103 .into_iter()
104 .map(|c| c.rel_path().to_path_buf())
105 .collect();
106 files.retain(|c| next.contains(c.rel_path()));
107 if files.is_empty() {
108 break;
109 }
110 }
111
112 files
113 }
114
115 fn candidates_multi(
117 indexes: &[Index],
118 query: &crate::query::QuerySpec<'_>,
119 ) -> Option<Vec<crate::Candidate>> {
120 use rayon::prelude::*;
121
122 let sets: Vec<Vec<crate::Candidate>> = indexes
123 .par_iter()
124 .filter_map(|idx| idx.candidates(query))
125 .collect();
126
127 if sets.is_empty() {
128 return None;
129 }
130
131 let mut result = sets.into_iter();
132 let mut current = result.next()?;
133
134 for next in result {
135 let lookup: HashSet<&Path> = next.iter().map(crate::Candidate::rel_path).collect();
136 current.retain(|c| lookup.contains(c.rel_path()));
137 if current.is_empty() {
138 break;
139 }
140 }
141
142 Some(current)
143 }
144
145 #[must_use]
146 pub fn first(&self) -> Option<&Index> {
147 self.snapshot.indexes().first()
148 }
149
150 #[must_use]
151 pub fn corpus_kind(&self) -> Option<CorpusKind> {
152 let indexes = self.snapshot.indexes();
153 let kind = indexes.first()?.corpus_kind();
154 if indexes.iter().any(|idx| idx.corpus_kind() != kind) {
155 return None;
156 }
157 Some(kind)
158 }
159}