use std::io::{Read, Seek, SeekFrom};
use flate2::Decompress;
use rayon::prelude::*;
use crate::error::{Result, SpssError};
use crate::io_utils::SavReader;
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct ZHeader {
pub zheader_offset: i64,
pub ztrailer_offset: i64,
pub ztrailer_length: i64,
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct ZTrailer {
pub bias: i64,
pub zero: i64,
pub block_size: i32,
pub n_blocks: i32,
pub entries: Vec<ZTrailerEntry>,
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct ZTrailerEntry {
pub uncompressed_offset: i64,
pub compressed_offset: i64,
pub uncompressed_size: i32,
pub compressed_size: i32,
}
pub fn read_zheader<R: Read>(reader: &mut SavReader<R>) -> Result<ZHeader> {
Ok(ZHeader {
zheader_offset: reader.read_i64()?,
ztrailer_offset: reader.read_i64()?,
ztrailer_length: reader.read_i64()?,
})
}
pub fn read_ztrailer<R: Read + Seek>(
reader: &mut SavReader<R>,
zheader: &ZHeader,
) -> Result<ZTrailer> {
reader
.inner_mut()
.seek(SeekFrom::Start(zheader.ztrailer_offset as u64))?;
let bias = reader.read_i64()?;
let zero = reader.read_i64()?;
let block_size = reader.read_i32()?;
let n_blocks = reader.read_i32()?;
let mut entries = Vec::with_capacity(n_blocks as usize);
for _ in 0..n_blocks {
entries.push(ZTrailerEntry {
uncompressed_offset: reader.read_i64()?,
compressed_offset: reader.read_i64()?,
uncompressed_size: reader.read_i32()?,
compressed_size: reader.read_i32()?,
});
}
Ok(ZTrailer {
bias,
zero,
block_size,
n_blocks,
entries,
})
}
pub fn decompress_single_block<R: Read + Seek>(
reader: &mut SavReader<R>,
entry: &ZTrailerEntry,
) -> Result<Vec<u8>> {
reader
.inner_mut()
.seek(SeekFrom::Start(entry.compressed_offset as u64))?;
let compressed = reader.read_bytes(entry.compressed_size as usize)?;
let mut output = vec![0u8; entry.uncompressed_size as usize];
let mut decompressor = Decompress::new(true);
match decompressor.decompress(&compressed, &mut output, flate2::FlushDecompress::Finish) {
Ok(flate2::Status::Ok | flate2::Status::StreamEnd) => Ok(output),
Ok(flate2::Status::BufError) => Err(SpssError::Zlib(
"single block decompression buffer too small".to_string(),
)),
Err(e) => Err(SpssError::Zlib(format!(
"single block decompression failed: {e}"
))),
}
}
#[allow(dead_code)]
pub fn decompress_zsav_blocks<R: Read + Seek>(
reader: &mut SavReader<R>,
trailer: &ZTrailer,
) -> Result<Vec<u8>> {
let mut compressed_blocks: Vec<(Vec<u8>, usize, usize)> =
Vec::with_capacity(trailer.entries.len());
let mut total_uncompressed: usize = 0;
for entry in &trailer.entries {
reader
.inner_mut()
.seek(SeekFrom::Start(entry.compressed_offset as u64))?;
let compressed = reader.read_bytes(entry.compressed_size as usize)?;
let uncompressed_size = entry.uncompressed_size as usize;
compressed_blocks.push((compressed, uncompressed_size, total_uncompressed));
total_uncompressed += uncompressed_size;
}
let mut output = vec![0u8; total_uncompressed];
let base_addr = output.as_mut_ptr() as usize;
compressed_blocks
.par_iter()
.try_for_each(|(compressed, uncompressed_size, offset)| {
let dest = unsafe {
std::slice::from_raw_parts_mut((base_addr + *offset) as *mut u8, *uncompressed_size)
};
let mut decompressor = Decompress::new(true);
match decompressor.decompress(compressed, dest, flate2::FlushDecompress::Finish) {
Ok(flate2::Status::Ok | flate2::Status::StreamEnd) => Ok(()),
Ok(flate2::Status::BufError) => Err(SpssError::Zlib(
"decompression buffer too small".to_string(),
)),
Err(e) => Err(SpssError::Zlib(format!("zlib decompression error: {e}"))),
}
})?;
Ok(output)
}