use std::io::{self, Write};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct GziEntry {
pub compressed_offset: u64,
pub uncompressed_offset: u64,
}
#[derive(Debug, Default)]
pub struct GziIndexBuilder {
entries: Vec<GziEntry>,
current_compressed_offset: u64,
current_uncompressed_offset: u64,
}
impl GziIndexBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn add_block(&mut self, compressed_size: u64, uncompressed_size: u64) {
self.entries.push(GziEntry {
compressed_offset: self.current_compressed_offset,
uncompressed_offset: self.current_uncompressed_offset,
});
self.current_compressed_offset += compressed_size;
self.current_uncompressed_offset += uncompressed_size;
}
pub fn compressed_offset(&self) -> u64 {
self.current_compressed_offset
}
pub fn uncompressed_offset(&self) -> u64 {
self.current_uncompressed_offset
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn entries(&self) -> &[GziEntry] {
&self.entries
}
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
writer.write_all(&(self.entries.len() as u64).to_le_bytes())?;
for entry in &self.entries {
writer.write_all(&entry.compressed_offset.to_le_bytes())?;
writer.write_all(&entry.uncompressed_offset.to_le_bytes())?;
}
Ok(())
}
pub fn reset(&mut self) {
self.entries.clear();
self.current_compressed_offset = 0;
self.current_uncompressed_offset = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gzi_builder_basic() {
let mut builder = GziIndexBuilder::new();
builder.add_block(100, 1000); builder.add_block(150, 2000); builder.add_block(120, 1500);
assert_eq!(builder.len(), 3);
assert_eq!(builder.compressed_offset(), 370);
assert_eq!(builder.uncompressed_offset(), 4500);
let entries = builder.entries();
assert_eq!(entries[0].compressed_offset, 0);
assert_eq!(entries[0].uncompressed_offset, 0);
assert_eq!(entries[1].compressed_offset, 100);
assert_eq!(entries[1].uncompressed_offset, 1000);
assert_eq!(entries[2].compressed_offset, 250);
assert_eq!(entries[2].uncompressed_offset, 3000);
}
#[test]
fn test_gzi_write() {
let mut builder = GziIndexBuilder::new();
builder.add_block(100, 1000);
builder.add_block(200, 2000);
let mut output = Vec::new();
builder.write(&mut output).unwrap();
assert_eq!(output.len(), 40);
let count = u64::from_le_bytes(output[0..8].try_into().unwrap());
assert_eq!(count, 2);
let c0 = u64::from_le_bytes(output[8..16].try_into().unwrap());
let u0 = u64::from_le_bytes(output[16..24].try_into().unwrap());
assert_eq!(c0, 0);
assert_eq!(u0, 0);
let c1 = u64::from_le_bytes(output[24..32].try_into().unwrap());
let u1 = u64::from_le_bytes(output[32..40].try_into().unwrap());
assert_eq!(c1, 100);
assert_eq!(u1, 1000);
}
}