gix_pack/data/output/
bytes.rs1use std::io::Write;
2
3use gix_features::hash;
4
5use crate::data::output;
6use crate::exact_vec;
7
8#[allow(missing_docs)]
10#[derive(Debug, thiserror::Error)]
11pub enum Error<E>
12where
13 E: std::error::Error + 'static,
14{
15 #[error(transparent)]
16 Io(#[from] std::io::Error),
17 #[error(transparent)]
18 Input(E),
19}
20
21pub struct FromEntriesIter<I, W> {
24 pub input: I,
26 output: hash::Write<W>,
28 trailer: Option<gix_hash::ObjectId>,
30 header_info: Option<(crate::data::Version, u32)>,
33 entry_version: crate::data::Version,
35 written: u64,
37 pack_offsets_and_validity: Vec<(u64, bool)>,
41 is_done: bool,
43}
44
45impl<I, W, E> FromEntriesIter<I, W>
46where
47 I: Iterator<Item = Result<Vec<output::Entry>, E>>,
48 W: std::io::Write,
49 E: std::error::Error + 'static,
50{
51 pub fn new(
62 input: I,
63 output: W,
64 num_entries: u32,
65 version: crate::data::Version,
66 object_hash: gix_hash::Kind,
67 ) -> Self {
68 assert!(
69 matches!(version, crate::data::Version::V2),
70 "currently only pack version 2 can be written",
71 );
72 FromEntriesIter {
73 input,
74 output: hash::Write::new(output, object_hash),
75 trailer: None,
76 entry_version: version,
77 pack_offsets_and_validity: exact_vec(num_entries as usize),
78 written: 0,
79 header_info: Some((version, num_entries)),
80 is_done: false,
81 }
82 }
83
84 pub fn into_write(self) -> W {
88 self.output.inner
89 }
90
91 pub fn digest(&self) -> Option<gix_hash::ObjectId> {
94 self.trailer
95 }
96
97 fn next_inner(&mut self) -> Result<u64, Error<E>> {
98 let previous_written = self.written;
99 if let Some((version, num_entries)) = self.header_info.take() {
100 let header_bytes = crate::data::header::encode(version, num_entries);
101 self.output.write_all(&header_bytes[..])?;
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.write_to(entry.decompressed_size as u64, &mut self.output)? as u64;
120 self.written += std::io::copy(&mut &*entry.compressed_data, &mut self.output)?;
121 }
122 }
123 None => {
124 let digest = self.output.hash.clone().digest();
125 self.output.inner.write_all(&digest[..])?;
126 self.written += digest.len() as u64;
127 self.output.inner.flush()?;
128 self.is_done = true;
129 self.trailer = Some(gix_hash::ObjectId::from(digest));
130 }
131 };
132 Ok(self.written - previous_written)
133 }
134}
135
136impl<I, W, E> Iterator for FromEntriesIter<I, W>
137where
138 I: Iterator<Item = Result<Vec<output::Entry>, E>>,
139 W: std::io::Write,
140 E: std::error::Error + 'static,
141{
142 type Item = Result<u64, Error<E>>;
144
145 fn next(&mut self) -> Option<Self::Item> {
146 if self.is_done {
147 return None;
148 }
149 Some(match self.next_inner() {
150 Err(err) => {
151 self.is_done = true;
152 Err(err)
153 }
154 Ok(written) => Ok(written),
155 })
156 }
157}