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 {}