rustic_core/index.rs
1use std::{num::NonZeroU32, sync::Arc, thread::sleep, time::Duration};
2
3use bytes::Bytes;
4use derive_more::Constructor;
5
6use crate::{
7 backend::{FileType, decrypt::DecryptReadBackend},
8 blob::{BlobId, BlobType, DataId, tree::TreeId},
9 error::{ErrorKind, RusticError, RusticResult},
10 index::binarysorted::{Index, IndexCollector, IndexType},
11 progress::Progress,
12 repofile::{
13 indexfile::{IndexBlob, IndexFile},
14 packfile::PackId,
15 },
16};
17
18pub(crate) mod binarysorted;
19pub(crate) mod indexer;
20
21/// An entry in the index
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Constructor)]
23pub struct IndexEntry {
24 /// The type of the blob
25 blob_type: BlobType,
26 /// The pack the blob is in
27 pub pack: PackId,
28 /// The offset of the blob in the pack
29 pub offset: u32,
30 /// The length of the blob in the pack
31 pub length: u32,
32 /// The uncompressed length of the blob
33 pub uncompressed_length: Option<NonZeroU32>,
34}
35
36impl IndexEntry {
37 /// Create an [`IndexEntry`] from an [`IndexBlob`]
38 ///
39 /// # Arguments
40 ///
41 /// * `blob` - The [`IndexBlob`] to create the [`IndexEntry`] from
42 /// * `pack` - The pack the blob is in
43 #[must_use]
44 pub const fn from_index_blob(blob: &IndexBlob, pack: PackId) -> Self {
45 Self {
46 blob_type: blob.tpe,
47 pack,
48 offset: blob.offset,
49 length: blob.length,
50 uncompressed_length: blob.uncompressed_length,
51 }
52 }
53
54 /// Get a blob described by [`IndexEntry`] from the backend
55 ///
56 /// # Arguments
57 ///
58 /// * `be` - The backend to read from
59 ///
60 /// # Errors
61 ///
62 // TODO: add error! This function will return an error if the blob is not found in the backend.
63 pub fn read_data<B: DecryptReadBackend>(&self, be: &B) -> RusticResult<Bytes> {
64 let data = be.read_encrypted_partial(
65 FileType::Pack,
66 &self.pack,
67 self.blob_type.is_cacheable(),
68 self.offset,
69 self.length,
70 self.uncompressed_length,
71 )?;
72
73 Ok(data)
74 }
75
76 /// Get the length of the data described by the [`IndexEntry`]
77 #[must_use]
78 pub const fn data_length(&self) -> u32 {
79 match self.uncompressed_length {
80 None => self.length - 32, // crypto overhead
81 Some(length) => length.get(),
82 }
83 }
84}
85
86/// The index of the repository
87///
88/// The index is a list of [`IndexEntry`]s
89pub trait ReadIndex {
90 /// Get an [`IndexEntry`] from the index
91 ///
92 /// # Arguments
93 ///
94 /// * `tpe` - The type of the blob
95 /// * `id` - The id of the blob
96 ///
97 /// # Returns
98 ///
99 /// The [`IndexEntry`] - If it exists otherwise `None`
100 fn get_id(&self, tpe: BlobType, id: &BlobId) -> Option<IndexEntry>;
101
102 /// Get the total size of all blobs of the given type
103 ///
104 /// # Arguments
105 ///
106 /// * `tpe` - The type of the blobs
107 fn total_size(&self, tpe: BlobType) -> u64;
108
109 /// Check if the index contains the given blob
110 ///
111 /// # Arguments
112 ///
113 /// * `tpe` - The type of the blob
114 /// * `id` - The id of the blob
115 fn has(&self, tpe: BlobType, id: &BlobId) -> bool;
116
117 /// Get a tree from the index
118 ///
119 /// # Arguments
120 ///
121 /// * `id` - The id of the tree
122 ///
123 /// # Returns
124 ///
125 /// The [`IndexEntry`] of the tree if it exists otherwise `None`
126 fn get_tree(&self, id: &TreeId) -> Option<IndexEntry> {
127 self.get_id(BlobType::Tree, &BlobId::from(**id))
128 }
129
130 /// Get a data blob from the index
131 ///
132 /// # Arguments
133 ///
134 /// * `id` - The id of the data blob
135 ///
136 /// # Returns
137 ///
138 /// The [`IndexEntry`] of the data blob if it exists otherwise `None`
139 fn get_data(&self, id: &DataId) -> Option<IndexEntry> {
140 self.get_id(BlobType::Data, &BlobId::from(**id))
141 }
142
143 /// Check if the index contains the given tree
144 ///
145 /// # Arguments
146 ///
147 /// * `id` - The id of the tree
148 ///
149 /// # Returns
150 ///
151 /// `true` if the index contains the tree otherwise `false`
152 fn has_tree(&self, id: &TreeId) -> bool {
153 self.has(BlobType::Tree, &BlobId::from(**id))
154 }
155
156 /// Check if the index contains the given data blob
157 ///
158 /// # Arguments
159 ///
160 /// * `id` - The id of the data blob
161 ///
162 /// # Returns
163 ///
164 /// `true` if the index contains the data blob otherwise `false`
165 fn has_data(&self, id: &DataId) -> bool {
166 self.has(BlobType::Data, &BlobId::from(**id))
167 }
168
169 /// Get a blob from the backend
170 ///
171 /// # Arguments
172 ///
173 /// * `tpe` - The type of the blob
174 /// * `id` - The id of the blob
175 ///
176 /// # Errors
177 ///
178 /// * If the blob could not be found in the index
179 fn blob_from_backend(
180 &self,
181 be: &impl DecryptReadBackend,
182 tpe: BlobType,
183 id: &BlobId,
184 ) -> RusticResult<Bytes> {
185 self.get_id(tpe, id).map_or_else(
186 || {
187 Err(RusticError::new(
188 ErrorKind::Internal,
189 "Blob `{id}` with type `{type}` not found in index",
190 )
191 .attach_context("id", id.to_string())
192 .attach_context("type", tpe.to_string()))
193 },
194 |ie| ie.read_data(be),
195 )
196 }
197}
198
199/// A trait for a global index
200pub trait ReadGlobalIndex: ReadIndex + Clone + Sync + Send + 'static {}
201
202/// A global index
203#[derive(Clone, Debug)]
204pub struct GlobalIndex {
205 /// The atomic reference counted, sharable index.
206 index: Arc<Index>,
207}
208
209impl ReadIndex for GlobalIndex {
210 /// Get an [`IndexEntry`] from the index
211 ///
212 /// # Arguments
213 ///
214 /// * `tpe` - The type of the blob
215 /// * `id` - The id of the blob
216 ///
217 /// # Returns
218 ///
219 /// The [`IndexEntry`] - If it exists otherwise `None`
220 fn get_id(&self, tpe: BlobType, id: &BlobId) -> Option<IndexEntry> {
221 self.index.get_id(tpe, id)
222 }
223
224 /// Get the total size of all blobs of the given type
225 ///
226 /// # Arguments
227 ///
228 /// * `tpe` - The type of the blobs
229 fn total_size(&self, tpe: BlobType) -> u64 {
230 self.index.total_size(tpe)
231 }
232
233 /// Check if the index contains the given blob
234 ///
235 /// # Arguments
236 ///
237 /// * `tpe` - The type of the blob
238 /// * `id` - The id of the blob
239 ///
240 /// # Returns
241 ///
242 /// `true` if the index contains the blob otherwise `false`
243 fn has(&self, tpe: BlobType, id: &BlobId) -> bool {
244 self.index.has(tpe, id)
245 }
246}
247
248impl GlobalIndex {
249 /// Create a new [`GlobalIndex`] from an [`Index`]
250 ///
251 /// # Type Parameters
252 ///
253 /// * `BE` - The backend type
254 ///
255 /// # Arguments
256 ///
257 /// * `be` - The backend to read from
258 /// * `index` - The index to use
259 pub fn new_from_index(index: Index) -> Self {
260 Self {
261 index: Arc::new(index),
262 }
263 }
264
265 /// Create a new [`GlobalIndex`] from an [`IndexCollector`]
266 ///
267 /// # Arguments
268 ///
269 /// * `be` - The backend to read from
270 /// * `p` - The progress tracker
271 /// * `collector` - The [`IndexCollector`] to use
272 ///
273 /// # Errors
274 ///
275 /// * If the index could not be read
276 fn new_from_collector(
277 be: &impl DecryptReadBackend,
278 p: &impl Progress,
279 mut collector: IndexCollector,
280 ) -> RusticResult<Self> {
281 p.set_title("reading index...");
282 for index in be.stream_all::<IndexFile>(p)? {
283 collector.extend(index?.1.packs);
284 }
285
286 p.finish();
287
288 Ok(Self::new_from_index(collector.into_index()))
289 }
290
291 /// Create a new [`GlobalIndex`]
292 ///
293 /// # Arguments
294 ///
295 /// * `be` - The backend to read from
296 /// * `p` - The progress tracker
297 pub fn new(be: &impl DecryptReadBackend, p: &impl Progress) -> RusticResult<Self> {
298 Self::new_from_collector(be, p, IndexCollector::new(IndexType::Full))
299 }
300
301 /// Create a new [`GlobalIndex`] with only full trees
302 ///
303 /// # Arguments
304 ///
305 /// * `be` - The backend to read from
306 /// * `p` - The progress tracker
307 ///
308 /// # Errors
309 ///
310 /// * If the index could not be read
311 pub fn only_full_trees(be: &impl DecryptReadBackend, p: &impl Progress) -> RusticResult<Self> {
312 Self::new_from_collector(be, p, IndexCollector::new(IndexType::DataIds))
313 }
314
315 /// Convert the `Arc<Index>` to an Index
316 pub fn into_index(self) -> Index {
317 match Arc::try_unwrap(self.index) {
318 Ok(index) => index,
319 Err(arc) => {
320 // Seems index is still in use; this could be due to some threads using it which didn't yet completely shut down.
321 // sleep a bit to let threads using the index shut down, after this index should be available to unwrap
322 sleep(Duration::from_millis(100));
323 Arc::try_unwrap(arc).expect("index still in use")
324 }
325 }
326 }
327
328 pub(crate) fn drop_data(self) -> Self {
329 Self {
330 index: Arc::new(self.into_index().drop_data()),
331 }
332 }
333}
334
335impl ReadGlobalIndex for GlobalIndex {}