gix_pack/data/output/
bytes.rs1use std::io::Write;
2
3use crate::data::output;
4use crate::exact_vec;
5
6#[allow(missing_docs)]
8#[derive(Debug, thiserror::Error)]
9pub enum Error<E>
10where
11 E: std::error::Error + 'static,
12{
13 #[error(transparent)]
14 Io(#[from] gix_hash::io::Error),
15 #[error(transparent)]
16 Input(E),
17}
18
19pub struct FromEntriesIter<I, W> {
22 pub input: I,
24 output: gix_hash::io::Write<W>,
26 trailer: Option<gix_hash::ObjectId>,
28 header_info: Option<(crate::data::Version, u32)>,
31 entry_version: crate::data::Version,
33 written: u64,
35 pack_offsets_and_validity: Vec<(u64, bool)>,
39 is_done: bool,
41}
42
43impl<I, W, E> FromEntriesIter<I, W>
44where
45 I: Iterator<Item = Result<Vec<output::Entry>, E>>,
46 W: std::io::Write,
47 E: std::error::Error + 'static,
48{
49 pub fn new(
60 input: I,
61 output: W,
62 num_entries: u32,
63 version: crate::data::Version,
64 object_hash: gix_hash::Kind,
65 ) -> Self {
66 assert!(
67 matches!(version, crate::data::Version::V2),
68 "currently only pack version 2 can be written",
69 );
70 FromEntriesIter {
71 input,
72 output: gix_hash::io::Write::new(output, object_hash),
73 trailer: None,
74 entry_version: version,
75 pack_offsets_and_validity: exact_vec(num_entries as usize),
76 written: 0,
77 header_info: Some((version, num_entries)),
78 is_done: false,
79 }
80 }
81
82 pub fn into_write(self) -> W {
86 self.output.inner
87 }
88
89 pub fn digest(&self) -> Option<gix_hash::ObjectId> {
92 self.trailer
93 }
94
95 fn next_inner(&mut self) -> Result<u64, Error<E>> {
96 let previous_written = self.written;
97 if let Some((version, num_entries)) = self.header_info.take() {
98 let header_bytes = crate::data::header::encode(version, num_entries);
99 self.output
100 .write_all(&header_bytes[..])
101 .map_err(gix_hash::io::Error::from)?;
102 self.written += header_bytes.len() as u64;
103 }
104 match self.input.next() {
105 Some(entries) => {
106 for entry in entries.map_err(Error::Input)? {
107 if entry.is_invalid() {
108 self.pack_offsets_and_validity.push((0, false));
109 continue;
110 }
111 self.pack_offsets_and_validity.push((self.written, true));
112 let header = entry.to_entry_header(self.entry_version, |index| {
113 let (base_offset, is_valid_object) = self.pack_offsets_and_validity[index];
114 if !is_valid_object {
115 unreachable!("if you see this the object database is correct as a delta refers to a non-existing object")
116 }
117 self.written - base_offset
118 });
119 self.written += header
120 .write_to(entry.decompressed_size as u64, &mut self.output)
121 .map_err(gix_hash::io::Error::from)? as u64;
122 self.written += std::io::copy(&mut &*entry.compressed_data, &mut self.output)
123 .map_err(gix_hash::io::Error::from)?;
124 }
125 }
126 None => {
127 let digest = self
128 .output
129 .hash
130 .clone()
131 .try_finalize()
132 .map_err(gix_hash::io::Error::from)?;
133 self.output
134 .inner
135 .write_all(digest.as_slice())
136 .map_err(gix_hash::io::Error::from)?;
137 self.written += digest.as_slice().len() as u64;
138 self.output.inner.flush().map_err(gix_hash::io::Error::from)?;
139 self.is_done = true;
140 self.trailer = Some(digest);
141 }
142 }
143 Ok(self.written - previous_written)
144 }
145}
146
147impl<I, W, E> Iterator for FromEntriesIter<I, W>
148where
149 I: Iterator<Item = Result<Vec<output::Entry>, E>>,
150 W: std::io::Write,
151 E: std::error::Error + 'static,
152{
153 type Item = Result<u64, Error<E>>;
155
156 fn next(&mut self) -> Option<Self::Item> {
157 if self.is_done {
158 return None;
159 }
160 Some(match self.next_inner() {
161 Err(err) => {
162 self.is_done = true;
163 Err(err)
164 }
165 Ok(written) => Ok(written),
166 })
167 }
168}