use std::io::{self, Read};
use crate::{
error::Error,
extract::{bcj, checksum::Hasher},
records::dataentry::{DataEntry, DataFlag},
version::Version,
};
pub struct FileReader<'a> {
bytes: FileBytes<'a>,
pos: usize,
hasher: Option<Hasher>,
finalize_error: Option<Error>,
}
enum FileBytes<'a> {
Borrowed(&'a [u8]),
Owned(Vec<u8>),
}
impl<'a> FileBytes<'a> {
fn as_slice(&self) -> &[u8] {
match self {
Self::Borrowed(s) => s,
Self::Owned(v) => v.as_slice(),
}
}
}
impl<'a> FileReader<'a> {
pub(crate) fn new(
chunk_bytes: &'a [u8],
data: &DataEntry,
version: &Version,
) -> Result<Self, Error> {
let start = usize::try_from(data.chunk_sub_offset).map_err(|_| Error::Overflow {
what: "chunk_sub_offset",
})?;
let len = usize::try_from(data.original_size).map_err(|_| Error::Overflow {
what: "original_size",
})?;
let end = start.checked_add(len).ok_or(Error::Overflow {
what: "file end offset",
})?;
let slice = chunk_bytes.get(start..end).ok_or(Error::Truncated {
what: "file slice within chunk",
})?;
let bytes = if data.flags.contains(&DataFlag::CallInstructionOptimized) {
let mut owned = slice.to_vec();
let filter = bcj::Filter::for_version(version);
filter.apply(&mut owned)?;
FileBytes::Owned(owned)
} else {
FileBytes::Borrowed(slice)
};
Ok(Self {
bytes,
pos: 0,
hasher: Some(Hasher::from_data_checksum(&data.checksum)),
finalize_error: None,
})
}
#[must_use]
pub fn len(&self) -> usize {
self.bytes.as_slice().len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl<'a> Read for FileReader<'a> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if let Some(err) = self.finalize_error.as_ref() {
return Err(io::Error::other(err.to_string()));
}
let src = self.bytes.as_slice();
let remaining = src.get(self.pos..).unwrap_or(&[]);
if remaining.is_empty() {
if let Some(h) = self.hasher.take()
&& let Err(e) = h.finalize()
{
let msg = e.to_string();
self.finalize_error = Some(e);
return Err(io::Error::other(msg));
}
return Ok(0);
}
let n = remaining.len().min(buf.len());
let chunk = remaining.get(..n).unwrap_or(&[]);
let dst = buf.get_mut(..n).unwrap_or(&mut []);
dst.copy_from_slice(chunk);
if let Some(h) = self.hasher.as_mut() {
h.update(chunk);
}
self.pos = self.pos.saturating_add(n);
Ok(n)
}
}
impl<'a> std::fmt::Debug for FileReader<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FileReader")
.field("len", &self.len())
.field("pos", &self.pos)
.field("hasher_active", &self.hasher.is_some())
.field(
"finalize_error",
&self.finalize_error.as_ref().map(ToString::to_string),
)
.finish()
}
}