rustic_core/repofile/
indexfile.rs

1use std::{cmp::Ordering, num::NonZeroU32};
2
3use chrono::{DateTime, Local};
4use serde_derive::{Deserialize, Serialize};
5use serde_with::skip_serializing_none;
6
7use crate::{
8    backend::FileType,
9    blob::{BlobId, BlobType},
10    impl_repoid,
11    repofile::{RepoFile, packfile::PackHeaderRef},
12};
13
14use super::packfile::PackId;
15
16impl_repoid!(IndexId, FileType::Index);
17
18/// Index files describe index information about multiple `pack` files.
19///
20/// They are usually stored in the repository under `/index/<ID>`
21#[skip_serializing_none]
22#[derive(Serialize, Deserialize, Debug, Default)]
23pub struct IndexFile {
24    /// which other index files are superseded by this (not actively used)
25    pub supersedes: Option<Vec<IndexId>>,
26    /// Index information about used packs
27    pub packs: Vec<IndexPack>,
28    #[serde(default, skip_serializing_if = "Vec::is_empty")]
29    /// Index information about unused packs which are already marked for deletion
30    pub packs_to_delete: Vec<IndexPack>,
31}
32
33impl RepoFile for IndexFile {
34    /// The [`FileType`] associated with the [`IndexFile`]
35    const TYPE: FileType = FileType::Index;
36    type Id = IndexId;
37}
38
39impl IndexFile {
40    /// Add a new pack to the index file
41    ///
42    /// # Arguments
43    ///
44    /// * `p` - The pack to add
45    /// * `delete` - If the pack should be marked for deletion
46    pub(crate) fn add(&mut self, p: IndexPack, delete: bool) {
47        if delete {
48            self.packs_to_delete.push(p);
49        } else {
50            self.packs.push(p);
51        }
52    }
53
54    pub(crate) fn all_packs(self) -> impl Iterator<Item = (IndexPack, bool)> {
55        self.packs
56            .into_iter()
57            .map(|pack| (pack, false))
58            .chain(self.packs_to_delete.into_iter().map(|pack| (pack, true)))
59    }
60}
61
62#[skip_serializing_none]
63#[derive(Serialize, Deserialize, Default, Debug, Clone)]
64/// Index information about a `pack`
65pub struct IndexPack {
66    /// pack Id
67    pub id: PackId,
68    /// Index information about contained blobs
69    pub blobs: Vec<IndexBlob>,
70    /// The pack creation time or time when the pack was marked for deletion
71    pub time: Option<DateTime<Local>>,
72    /// The pack size
73    pub size: Option<u32>,
74}
75
76impl IndexPack {
77    /// Add a new blob to the pack
78    ///
79    /// # Arguments
80    ///
81    /// * `id` - The blob id
82    /// * `tpe` - The blob type
83    /// * `offset` - The blob offset within the pack
84    /// * `length` - The blob length within the pack
85    /// * `uncompressed_length` - The blob uncompressed length within the pack
86    pub(crate) fn add(
87        &mut self,
88        id: BlobId,
89        tpe: BlobType,
90        offset: u32,
91        length: u32,
92        uncompressed_length: Option<NonZeroU32>,
93    ) {
94        self.blobs.push(IndexBlob {
95            id,
96            tpe,
97            offset,
98            length,
99            uncompressed_length,
100        });
101    }
102
103    /// Calculate the pack size from the contained blobs
104    #[must_use]
105    pub fn pack_size(&self) -> u32 {
106        self.size
107            .unwrap_or_else(|| PackHeaderRef::from_index_pack(self).pack_size())
108    }
109
110    /// Returns the blob type of the pack.
111    ///
112    /// # Note
113    ///
114    /// Only packs with identical blob types are allowed.
115    #[must_use]
116    pub fn blob_type(&self) -> BlobType {
117        // TODO: This is a hack to support packs without blobs (e.g. when deleting unreferenced files)
118        if self.blobs.is_empty() {
119            BlobType::Data
120        } else {
121            self.blobs[0].tpe
122        }
123    }
124}
125
126#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq, Copy)]
127/// Index information about a `blob`
128pub struct IndexBlob {
129    /// Blob Id
130    pub id: BlobId,
131    #[serde(rename = "type")]
132    /// Type of the blob
133    pub tpe: BlobType,
134    /// Offset of the blob within the `pack` file
135    pub offset: u32,
136    /// Length of the blob as stored within the `pack` file
137    pub length: u32,
138    /// Data length of the blob. This is only set if the blob is compressed.
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub uncompressed_length: Option<NonZeroU32>,
141}
142
143impl PartialOrd<Self> for IndexBlob {
144    /// Compare two blobs by their offset
145    ///
146    /// # Arguments
147    ///
148    /// * `other` - The other blob to compare to
149    ///
150    /// # Returns
151    ///
152    /// The ordering of the two blobs
153    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
154        Some(self.cmp(other))
155    }
156}
157
158impl Ord for IndexBlob {
159    /// Compare two blobs by their offset
160    ///
161    /// # Arguments
162    ///
163    /// * `other` - The other blob to compare to
164    ///
165    /// # Returns
166    ///
167    /// The ordering of the two blobs
168    fn cmp(&self, other: &Self) -> Ordering {
169        self.offset.cmp(&other.offset)
170    }
171}