grit_lib/odb/store/
midx.rs1use std::collections::HashSet;
7use std::io::Cursor;
8use std::ops::ControlFlow;
9use std::sync::{Arc, RwLock};
10
11use crate::diagnostics::NullDiagnostics;
12use crate::error::{Error, Result};
13use crate::midx::{prepared_midx_chain_strict, PreparedMidxChain};
14use crate::objects::{HashAlgo, Object, ObjectId, ObjectIdPrefix, ObjectInfo};
15use crate::pack::clear_pack_cache;
16use crate::pack_store::PackStore;
17
18use super::{ObjectStore, ObjectStream, PackedObjects};
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum MidxObjectsStatus {
23 Disabled,
25 Active,
27 Unusable,
29}
30
31pub struct MidxObjects {
33 packs: PackedObjects,
34 multi_pack_index: bool,
35 status: RwLock<MidxObjectsStatus>,
36}
37
38impl std::fmt::Debug for MidxObjects {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 f.debug_struct("MidxObjects")
41 .field("objects_dir", &self.packs.pack_store().objects_dir())
42 .field("multi_pack_index", &self.multi_pack_index)
43 .field("status", &self.status())
44 .finish()
45 }
46}
47
48impl MidxObjects {
49 pub fn new(store: Arc<PackStore>, multi_pack_index: bool) -> Result<Self> {
58 let packs = PackedObjects::new(Arc::clone(&store));
59 let status = if multi_pack_index {
60 classify_midx_status(store.objects_dir())?
61 } else {
62 MidxObjectsStatus::Disabled
63 };
64 Ok(Self {
65 packs,
66 multi_pack_index,
67 status: RwLock::new(status),
68 })
69 }
70
71 #[must_use]
73 pub fn status(&self) -> MidxObjectsStatus {
74 self.status
75 .read()
76 .map(|g| *g)
77 .unwrap_or(MidxObjectsStatus::Unusable)
78 }
79
80 #[must_use]
82 pub fn multi_pack_index_enabled(&self) -> bool {
83 self.multi_pack_index
84 }
85
86 pub fn covered_pack_basenames(&self) -> Result<HashSet<String>> {
94 let Some(chain) = self.active_chain()? else {
95 return Ok(HashSet::new());
96 };
97 Ok(chain.covered_pack_basenames())
98 }
99
100 #[must_use]
102 pub fn packed_objects(&self) -> &PackedObjects {
103 &self.packs
104 }
105
106 fn active_chain(&self) -> Result<Option<Arc<PreparedMidxChain>>> {
107 if self.status() != MidxObjectsStatus::Active {
108 return Ok(None);
109 }
110 match prepared_midx_chain_strict(self.packs.pack_store().objects_dir()) {
111 Ok(chain) => Ok(chain),
112 Err(Error::Midx(_)) => Ok(None),
113 Err(err) => Err(err),
114 }
115 }
116
117 fn read_via_chain(&self, oid: &ObjectId) -> Result<Option<Object>> {
118 let Some(chain) = self.active_chain()? else {
119 return Ok(None);
120 };
121 chain.try_read_object(oid, &NullDiagnostics)
122 }
123
124 fn read_info_via_chain(&self, oid: &ObjectId) -> Result<Option<ObjectInfo>> {
125 let Some(chain) = self.active_chain()? else {
126 return Ok(None);
127 };
128 chain.try_read_info(oid, &NullDiagnostics)
129 }
130}
131
132impl ObjectStore for MidxObjects {
133 fn hash_algo(&self) -> HashAlgo {
134 self.packs.hash_algo()
135 }
136
137 fn read(&self, oid: &ObjectId) -> Result<Option<Object>> {
138 self.read_via_chain(oid)
139 }
140
141 fn read_info(&self, oid: &ObjectId) -> Result<Option<ObjectInfo>> {
142 self.read_info_via_chain(oid)
143 }
144
145 fn open_stream(&self, oid: &ObjectId) -> Result<Option<ObjectStream<'_>>> {
146 let Some(info) = self.read_info(oid)? else {
147 return Ok(None);
148 };
149 if let Some(stream) = self.packs.open_stream(oid)? {
150 return Ok(Some(stream));
151 }
152 let Some(obj) = self.read(oid)? else {
153 return Ok(None);
154 };
155 let size = u64::try_from(obj.data.len()).map_err(|_| {
156 Error::CorruptObject(format!("object size overflow for {}", oid.to_hex()))
157 })?;
158 Ok(Some(ObjectStream {
159 kind: info.kind,
160 size,
161 reader: Box::new(Cursor::new(obj.data)),
162 }))
163 }
164
165 fn for_each_object(&self, f: &mut dyn FnMut(&ObjectId) -> ControlFlow<()>) -> Result<()> {
166 let Some(chain) = self.active_chain()? else {
167 return Ok(());
168 };
169 chain.for_each_listed_object(f)
170 }
171
172 fn lookup_prefix(&self, prefix: &str, limit: usize, out: &mut Vec<ObjectId>) -> Result<()> {
173 if prefix.len() > self.hash_algo().hex_len() {
174 return Ok(());
175 }
176 let Some(chain) = self.active_chain()? else {
177 return Ok(());
178 };
179 let prefix = ObjectIdPrefix::from_hex(prefix, self.hash_algo())?;
180 chain.lookup_prefix(&prefix, limit, out)
181 }
182
183 fn refresh(&self) -> Result<bool> {
184 if !self.multi_pack_index {
185 return Ok(false);
186 }
187 let objects_dir = self.packs.pack_store().objects_dir();
188 clear_pack_cache();
189 crate::midx::evict_midx_read_cache_for_pack_dir(&objects_dir.join("pack"));
190 let new_status = classify_midx_status(objects_dir)?;
191 let mut guard = self
192 .status
193 .write()
194 .map_err(|e| Error::Io(std::io::Error::other(e.to_string())))?;
195 let changed = *guard != new_status;
196 *guard = new_status;
197 Ok(changed)
198 }
199}
200
201fn classify_midx_status(objects_dir: &std::path::Path) -> Result<MidxObjectsStatus> {
202 match prepared_midx_chain_strict(objects_dir) {
203 Ok(Some(_)) => Ok(MidxObjectsStatus::Active),
204 Ok(None) | Err(Error::Midx(_)) | Err(Error::CorruptObject(_)) => {
205 Ok(MidxObjectsStatus::Unusable)
206 }
207 Err(err) => Err(err),
208 }
209}