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}