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_store::PackStore;
16
17use super::{ObjectStore, ObjectStream, PackedObjects};
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum MidxObjectsStatus {
22 Disabled,
24 Active,
26 Unusable,
28}
29
30pub struct MidxObjects {
39 packs: PackedObjects,
40 multi_pack_index: bool,
41 status: RwLock<MidxObjectsStatus>,
42}
43
44impl std::fmt::Debug for MidxObjects {
45 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46 f.debug_struct("MidxObjects")
47 .field("objects_dir", &self.packs.pack_store().objects_dir())
48 .field("multi_pack_index", &self.multi_pack_index)
49 .field("status", &self.status())
50 .finish()
51 }
52}
53
54impl MidxObjects {
55 pub fn new(store: Arc<PackStore>, multi_pack_index: bool) -> Result<Self> {
64 let packs = PackedObjects::new(Arc::clone(&store));
65 let status = if multi_pack_index {
66 PackStore::with_context(Arc::clone(&store), || {
67 classify_midx_status(store.objects_dir())
68 })?
69 } else {
70 MidxObjectsStatus::Disabled
71 };
72 Ok(Self {
73 packs,
74 multi_pack_index,
75 status: RwLock::new(status),
76 })
77 }
78
79 #[must_use]
81 pub fn status(&self) -> MidxObjectsStatus {
82 self.status
83 .read()
84 .map(|g| *g)
85 .unwrap_or(MidxObjectsStatus::Unusable)
86 }
87
88 #[must_use]
90 pub fn multi_pack_index_enabled(&self) -> bool {
91 self.multi_pack_index
92 }
93
94 pub fn covered_pack_basenames(&self) -> Result<HashSet<String>> {
102 let Some(chain) = self.active_chain()? else {
103 return Ok(HashSet::new());
104 };
105 Ok(chain.covered_pack_basenames())
106 }
107
108 #[must_use]
110 pub fn packed_objects(&self) -> &PackedObjects {
111 &self.packs
112 }
113
114 fn active_chain(&self) -> Result<Option<Arc<PreparedMidxChain>>> {
115 if self.status() != MidxObjectsStatus::Active {
116 return Ok(None);
117 }
118 let store = self.packs.pack_store();
119 match PackStore::with_context(Arc::clone(store), || {
120 prepared_midx_chain_strict(store.objects_dir())
121 }) {
122 Ok(chain) => Ok(chain),
123 Err(Error::Midx(_)) => Ok(None),
124 Err(err) => Err(err),
125 }
126 }
127
128 fn read_via_chain(&self, oid: &ObjectId) -> Result<Option<Object>> {
129 PackStore::with_context(Arc::clone(self.packs.pack_store()), || {
130 let Some(chain) = self.active_chain()? else {
131 return Ok(None);
132 };
133 chain.try_read_object(oid, &NullDiagnostics)
134 })
135 }
136
137 fn read_info_via_chain(&self, oid: &ObjectId) -> Result<Option<ObjectInfo>> {
138 PackStore::with_context(Arc::clone(self.packs.pack_store()), || {
139 let Some(chain) = self.active_chain()? else {
140 return Ok(None);
141 };
142 chain.try_read_info(oid, &NullDiagnostics)
143 })
144 }
145}
146
147impl ObjectStore for MidxObjects {
148 fn hash_algo(&self) -> HashAlgo {
149 self.packs.hash_algo()
150 }
151
152 fn read(&self, oid: &ObjectId) -> Result<Option<Object>> {
153 self.read_via_chain(oid)
154 }
155
156 fn read_info(&self, oid: &ObjectId) -> Result<Option<ObjectInfo>> {
157 self.read_info_via_chain(oid)
158 }
159
160 fn open_stream(&self, oid: &ObjectId) -> Result<Option<ObjectStream<'_>>> {
161 let Some(info) = self.read_info(oid)? else {
162 return Ok(None);
163 };
164 if let Some(stream) = self.packs.open_stream(oid)? {
165 return Ok(Some(stream));
166 }
167 let Some(obj) = self.read(oid)? else {
168 return Ok(None);
169 };
170 let size = u64::try_from(obj.data.len()).map_err(|_| {
171 Error::CorruptObject(format!("object size overflow for {}", oid.to_hex()))
172 })?;
173 Ok(Some(ObjectStream {
174 kind: info.kind,
175 size,
176 reader: Box::new(Cursor::new(obj.data)),
177 }))
178 }
179
180 fn for_each_object(&self, f: &mut dyn FnMut(&ObjectId) -> ControlFlow<()>) -> Result<()> {
181 let Some(chain) = self.active_chain()? else {
182 return Ok(());
183 };
184 chain.for_each_listed_object(f)
185 }
186
187 fn lookup_prefix(&self, prefix: &str, limit: usize, out: &mut Vec<ObjectId>) -> Result<()> {
188 if prefix.len() > self.hash_algo().hex_len() {
189 return Ok(());
190 }
191 let Some(chain) = self.active_chain()? else {
192 return Ok(());
193 };
194 let prefix = ObjectIdPrefix::from_hex(prefix, self.hash_algo())?;
195 chain.lookup_prefix(&prefix, limit, out)
196 }
197
198 fn refresh(&self) -> Result<bool> {
199 if !self.multi_pack_index {
200 return Ok(false);
201 }
202 let store = self.packs.pack_store();
203 store.invalidate_all();
204 let new_status = PackStore::with_context(Arc::clone(store), || {
205 classify_midx_status(store.objects_dir())
206 })?;
207 let mut guard = self
208 .status
209 .write()
210 .map_err(|e| Error::Io(std::io::Error::other(e.to_string())))?;
211 let changed = *guard != new_status;
212 *guard = new_status;
213 Ok(changed)
214 }
215}
216
217fn classify_midx_status(objects_dir: &std::path::Path) -> Result<MidxObjectsStatus> {
218 match prepared_midx_chain_strict(objects_dir) {
219 Ok(Some(_)) => Ok(MidxObjectsStatus::Active),
220 Ok(None) | Err(Error::Midx(_)) | Err(Error::CorruptObject(_)) => {
221 Ok(MidxObjectsStatus::Unusable)
222 }
223 Err(err) => Err(err),
224 }
225}