use std::iter::Peekable;
use gix_error::{ExnResult, message};
use crate::data::input;
pub struct EntriesToBytesIter<I: Iterator, W> {
pub input: Peekable<I>,
output: W,
trailer: Option<gix_hash::ObjectId>,
data_version: crate::data::Version,
num_entries: u32,
is_done: bool,
object_hash: gix_hash::Kind,
}
impl<I, W> EntriesToBytesIter<I, W>
where
I: Iterator<Item = ExnResult<input::Entry>>,
W: std::io::Read + std::io::Write + std::io::Seek,
{
pub fn new(input: I, output: W, version: crate::data::Version, object_hash: gix_hash::Kind) -> Self {
EntriesToBytesIter {
input: input.peekable(),
output,
object_hash,
num_entries: 0,
trailer: None,
data_version: version,
is_done: false,
}
}
pub fn digest(&self) -> Option<gix_hash::ObjectId> {
self.trailer
}
fn next_inner(&mut self, entry: input::Entry) -> ExnResult<input::Entry> {
if self.num_entries == 0 {
let header_bytes = crate::data::header::encode(self.data_version, 0);
self.output
.write_all(&header_bytes[..])
.map_err(gix_hash::io::from_std_io)?;
}
self.num_entries += 1;
entry
.header
.write_to(entry.decompressed_size, &mut self.output)
.map_err(gix_hash::io::from_std_io)?;
self.output
.write_all(
entry
.compressed
.as_deref()
.expect("caller must configure generator to keep compressed bytes"),
)
.map_err(gix_hash::io::from_std_io)?;
Ok(entry)
}
fn write_header_and_digest(&mut self, last_entry: Option<&mut input::Entry>) -> ExnResult {
let header_bytes = crate::data::header::encode(self.data_version, self.num_entries);
let num_bytes_written = if last_entry.is_some() {
self.output.stream_position().map_err(gix_hash::io::from_std_io)?
} else {
header_bytes.len() as u64
};
self.output.rewind().map_err(gix_hash::io::from_std_io)?;
self.output
.write_all(&header_bytes[..])
.map_err(gix_hash::io::from_std_io)?;
self.output.flush().map_err(gix_hash::io::from_std_io)?;
self.output.rewind().map_err(gix_hash::io::from_std_io)?;
let interrupt_never = std::sync::atomic::AtomicBool::new(false);
let digest = gix_hash::bytes(
&mut self.output,
num_bytes_written,
self.object_hash,
&mut gix_features::progress::Discard,
&interrupt_never,
)?;
self.output
.write_all(digest.as_slice())
.map_err(gix_hash::io::from_std_io)?;
self.output.flush().map_err(gix_hash::io::from_std_io)?;
self.is_done = true;
if let Some(last_entry) = last_entry {
last_entry.trailer = Some(digest);
}
self.trailer = Some(digest);
Ok(())
}
}
impl<I, W> Iterator for EntriesToBytesIter<I, W>
where
I: Iterator<Item = ExnResult<input::Entry>>,
W: std::io::Read + std::io::Write + std::io::Seek,
{
type Item = ExnResult<input::Entry>;
fn next(&mut self) -> Option<Self::Item> {
if self.is_done {
return None;
}
match self.input.next() {
Some(res) => Some(match res {
Ok(entry) => self
.next_inner(entry)
.and_then(|mut entry| {
if self.input.peek().is_none() {
self.write_header_and_digest(Some(&mut entry)).map(|_| entry)
} else {
Ok(entry)
}
})
.map_err(hash_io_error),
Err(err) => {
self.is_done = true;
Err(err)
}
}),
None => match self.write_header_and_digest(None) {
Ok(_) => None,
Err(err) => Some(Err(hash_io_error(err))),
},
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.input.size_hint()
}
}
fn hash_io_error(err: gix_error::Exn) -> gix_error::Exn {
err.raise(message("An IO operation failed while streaming an entry"))
.erased()
}