use std::io::{self, Read, Seek, SeekFrom};
use crate::format::SIGNATURE_HEADER_SIZE;
use crate::format::parser::ArchiveHeader;
use crate::format::streams::Folder;
use crate::{Error, READ_BUFFER_SIZE, Result};
#[cfg(feature = "aes")]
use crate::Password;
use super::config::StreamingConfig;
pub struct SolidBlockStreamReader<'a, R: Read + Seek> {
header: &'a ArchiveHeader,
source: &'a mut R,
block_index: usize,
#[cfg(feature = "aes")]
#[allow(dead_code)] password: &'a Password,
#[allow(dead_code)] config: StreamingConfig,
decoder: Option<Box<dyn Read + Send + 'a>>,
current_entry_index: usize,
entry_sizes: Vec<u64>,
bytes_remaining_in_entry: u64,
total_decompressed: u64,
initialized: bool,
}
impl<'a, R: Read + Seek + Send> SolidBlockStreamReader<'a, R> {
#[cfg(feature = "aes")]
pub fn new(
header: &'a ArchiveHeader,
source: &'a mut R,
block_index: usize,
password: &'a Password,
config: StreamingConfig,
) -> Result<Self> {
let entry_sizes = Self::collect_entry_sizes(header, block_index)?;
Ok(Self {
header,
source,
block_index,
password,
config,
decoder: None,
current_entry_index: 0,
entry_sizes,
bytes_remaining_in_entry: 0,
total_decompressed: 0,
initialized: false,
})
}
#[cfg(not(feature = "aes"))]
pub fn new(
header: &'a ArchiveHeader,
source: &'a mut R,
block_index: usize,
config: StreamingConfig,
) -> Result<Self> {
let entry_sizes = Self::collect_entry_sizes(header, block_index)?;
Ok(Self {
header,
source,
block_index,
config,
decoder: None,
current_entry_index: 0,
entry_sizes,
bytes_remaining_in_entry: 0,
total_decompressed: 0,
initialized: false,
})
}
fn collect_entry_sizes(header: &ArchiveHeader, block_index: usize) -> Result<Vec<u64>> {
let substreams = header.substreams_info.as_ref().ok_or_else(|| {
Error::InvalidFormat("missing substreams info for solid block".into())
})?;
let num_streams = *substreams
.num_unpack_streams_in_folders
.get(block_index)
.ok_or_else(|| {
Error::InvalidFormat(format!("block index {} out of range", block_index))
})? as usize;
let stream_offset: usize = substreams
.num_unpack_streams_in_folders
.iter()
.take(block_index)
.map(|&n| n as usize)
.sum();
let sizes: Vec<u64> = (0..num_streams)
.map(|i| {
substreams
.unpack_sizes
.get(stream_offset + i)
.copied()
.unwrap_or(0)
})
.collect();
Ok(sizes)
}
fn initialize(&mut self) -> Result<()> {
if self.initialized {
return Ok(());
}
let folders = match &self.header.unpack_info {
Some(ui) => &ui.folders,
None => return Err(Error::InvalidFormat("missing unpack info".into())),
};
let folder = folders.get(self.block_index).ok_or_else(|| {
Error::InvalidFormat(format!("block index {} out of range", self.block_index))
})?;
let pack_offset = self.calculate_block_offset()?;
self.source
.seek(SeekFrom::Start(pack_offset))
.map_err(Error::Io)?;
self.decoder = Some(self.build_decoder(folder)?);
self.initialized = true;
if !self.entry_sizes.is_empty() {
self.bytes_remaining_in_entry = self.entry_sizes[0];
}
Ok(())
}
fn calculate_block_offset(&self) -> Result<u64> {
let pack_info = self
.header
.pack_info
.as_ref()
.ok_or_else(|| Error::InvalidFormat("missing pack info".into()))?;
let mut offset = SIGNATURE_HEADER_SIZE + pack_info.pack_pos;
for i in 0..self.block_index {
if i < pack_info.pack_sizes.len() {
offset += pack_info.pack_sizes[i];
}
}
Ok(offset)
}
fn build_decoder(&mut self, folder: &Folder) -> Result<Box<dyn Read + Send + 'a>> {
if folder.coders.is_empty() {
return Err(Error::InvalidFormat("folder has no coders".into()));
}
let uncompressed_size = folder.final_unpack_size().unwrap_or(0);
let pack_size = self
.header
.pack_info
.as_ref()
.and_then(|pi| pi.pack_sizes.get(self.block_index).copied())
.unwrap_or(0);
let mut packed_data = vec![0u8; pack_size as usize];
self.source
.read_exact(&mut packed_data)
.map_err(Error::Io)?;
let cursor = std::io::Cursor::new(packed_data);
crate::codec::build_decoder_chain(cursor, folder, uncompressed_size)
}
pub fn num_entries(&self) -> usize {
self.entry_sizes.len()
}
pub fn current_index(&self) -> usize {
self.current_entry_index
}
pub fn is_exhausted(&self) -> bool {
self.current_entry_index >= self.entry_sizes.len()
}
pub fn total_decompressed(&self) -> u64 {
self.total_decompressed
}
pub fn next_entry(&mut self) -> Option<Result<(usize, u64)>> {
if self.is_exhausted() {
return None;
}
if !self.initialized {
if let Err(e) = self.initialize() {
return Some(Err(e));
}
}
let idx = self.current_entry_index;
let size = self.entry_sizes.get(idx).copied()?;
self.bytes_remaining_in_entry = size;
Some(Ok((idx, size)))
}
pub fn read_entry_data(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if self.bytes_remaining_in_entry == 0 {
return Ok(0);
}
let decoder = match &mut self.decoder {
Some(d) => d,
None => return Ok(0),
};
let to_read = buf.len().min(self.bytes_remaining_in_entry as usize);
let n = decoder.read(&mut buf[..to_read])?;
self.bytes_remaining_in_entry -= n as u64;
self.total_decompressed += n as u64;
Ok(n)
}
pub fn finish_entry(&mut self) -> Result<()> {
if self.bytes_remaining_in_entry > 0 {
let decoder = match &mut self.decoder {
Some(d) => d,
None => return Ok(()),
};
let remaining = self.bytes_remaining_in_entry;
io::copy(&mut decoder.take(remaining), &mut io::sink()).map_err(Error::Io)?;
self.total_decompressed += remaining;
self.bytes_remaining_in_entry = 0;
}
self.current_entry_index += 1;
if self.current_entry_index < self.entry_sizes.len() {
self.bytes_remaining_in_entry = self.entry_sizes[self.current_entry_index];
}
Ok(())
}
pub fn skip_current_entry(&mut self) -> Result<()> {
self.finish_entry()
}
pub fn read_entry_to_vec(&mut self) -> Result<Vec<u8>> {
let size = self.bytes_remaining_in_entry as usize;
let mut data = Vec::with_capacity(size);
loop {
let mut buf = [0u8; READ_BUFFER_SIZE];
let n = self.read_entry_data(&mut buf)?;
if n == 0 {
break;
}
data.extend_from_slice(&buf[..n]);
}
self.current_entry_index += 1;
if self.current_entry_index < self.entry_sizes.len() {
self.bytes_remaining_in_entry = self.entry_sizes[self.current_entry_index];
}
Ok(data)
}
pub fn extract_entry_to<W: io::Write>(&mut self, writer: &mut W) -> Result<u64> {
let mut total = 0u64;
let mut buf = [0u8; READ_BUFFER_SIZE];
loop {
let n = self.read_entry_data(&mut buf)?;
if n == 0 {
break;
}
writer.write_all(&buf[..n]).map_err(Error::Io)?;
total += n as u64;
}
self.current_entry_index += 1;
if self.current_entry_index < self.entry_sizes.len() {
self.bytes_remaining_in_entry = self.entry_sizes[self.current_entry_index];
}
Ok(total)
}
}
#[derive(Debug, Clone)]
pub struct SolidBlockInfo {
pub block_index: usize,
pub num_entries: usize,
pub total_size: u64,
pub packed_size: u64,
pub entry_sizes: Vec<u64>,
}
impl SolidBlockInfo {
pub fn from_header(header: &ArchiveHeader, block_index: usize) -> Result<Self> {
let substreams = header
.substreams_info
.as_ref()
.ok_or_else(|| Error::InvalidFormat("missing substreams info".into()))?;
let num_entries = substreams
.num_unpack_streams_in_folders
.get(block_index)
.copied()
.unwrap_or(0) as usize;
let folders = header.unpack_info.as_ref().map(|ui| &ui.folders);
let total_size = folders
.and_then(|f| f.get(block_index))
.and_then(|folder| folder.final_unpack_size())
.unwrap_or(0);
let packed_size = header
.pack_info
.as_ref()
.and_then(|pi| pi.pack_sizes.get(block_index).copied())
.unwrap_or(0);
let stream_offset: usize = substreams
.num_unpack_streams_in_folders
.iter()
.take(block_index)
.map(|&n| n as usize)
.sum();
let entry_sizes: Vec<u64> = (0..num_entries)
.map(|i| {
substreams
.unpack_sizes
.get(stream_offset + i)
.copied()
.unwrap_or(0)
})
.collect();
Ok(Self {
block_index,
num_entries,
total_size,
packed_size,
entry_sizes,
})
}
pub fn compression_ratio(&self) -> f64 {
if self.packed_size == 0 {
0.0
} else {
self.total_size as f64 / self.packed_size as f64
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_solid_block_info_compression_ratio() {
let info = SolidBlockInfo {
block_index: 0,
num_entries: 5,
total_size: 1000,
packed_size: 100,
entry_sizes: vec![200, 200, 200, 200, 200],
};
assert!((info.compression_ratio() - 10.0).abs() < f64::EPSILON);
}
#[test]
fn test_solid_block_info_zero_packed() {
let info = SolidBlockInfo {
block_index: 0,
num_entries: 0,
total_size: 0,
packed_size: 0,
entry_sizes: vec![],
};
assert!((info.compression_ratio() - 0.0).abs() < f64::EPSILON);
}
}