use std::{
fs::File,
io::{self, BufWriter, Write},
path::Path,
};
use bytes::BytesMut;
use libdeflater::Crc;
use crate::{
header_inner, CompressionLevel, Compressor, BGZF_BLOCK_SIZE, BGZF_EOF, BGZF_FOOTER_SIZE,
BGZF_HEADER_SIZE, BGZF_SIZEOF_CRC32, BUFSIZE, DEFLATE_STORED_HEADER_SIZE, MAX_BGZF_BLOCK_SIZE,
};
pub struct Writer<W>
where
W: Write,
{
uncompressed_buffer: BytesMut,
compressed_buffer: Vec<u8>,
blocksize: usize,
level: CompressionLevel,
compressor: Compressor,
store_only: bool,
store_crc: Crc,
store_data_len: usize,
writer: Option<W>,
}
impl<W> Writer<W>
where
W: Write,
{
pub fn new(writer: W, compression_level: CompressionLevel) -> Self {
Self::with_capacity(writer, compression_level, BGZF_BLOCK_SIZE)
}
pub fn with_capacity(writer: W, compression_level: CompressionLevel, blocksize: usize) -> Self {
assert!(
(1..=BGZF_BLOCK_SIZE).contains(&blocksize),
"blocksize must be in 1..={BGZF_BLOCK_SIZE}"
);
let compressor = Compressor::new(compression_level);
let store_only = u8::from(compression_level) == 0;
let compressed_buffer = if store_only {
vec![0u8; BGZF_HEADER_SIZE + DEFLATE_STORED_HEADER_SIZE + blocksize + BGZF_FOOTER_SIZE]
} else {
Vec::with_capacity(BUFSIZE)
};
Self {
uncompressed_buffer: BytesMut::with_capacity(BUFSIZE),
compressed_buffer,
blocksize,
level: compression_level,
compressor,
store_only,
store_crc: Crc::new(),
store_data_len: 0,
writer: Some(writer),
}
}
pub fn finish(mut self) -> io::Result<W> {
self.flush_buffer()?;
let mut writer = self.writer.take().expect("writer already taken");
writer.write_all(BGZF_EOF)?;
writer.flush()?;
Ok(writer)
}
fn flush_buffer(&mut self) -> io::Result<()> {
if self.store_only {
if self.store_data_len > 0 {
self.emit_store_block()?;
}
return Ok(());
}
let writer = self
.writer
.as_mut()
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "writer already finished"))?;
while !self.uncompressed_buffer.is_empty() {
let b = self
.uncompressed_buffer
.split_to(std::cmp::min(self.uncompressed_buffer.len(), MAX_BGZF_BLOCK_SIZE))
.freeze();
self.compressor
.compress(&b[..], &mut self.compressed_buffer)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
writer.write_all(&self.compressed_buffer)?;
self.compressed_buffer.clear();
}
Ok(())
}
fn write_store_only(&mut self, buf: &[u8]) -> io::Result<usize> {
let data_offset = BGZF_HEADER_SIZE + DEFLATE_STORED_HEADER_SIZE;
let mut remaining = buf;
while !remaining.is_empty() {
let n = (self.blocksize - self.store_data_len).min(remaining.len());
let start = data_offset + self.store_data_len;
self.compressed_buffer[start..start + n].copy_from_slice(&remaining[..n]);
self.store_crc.update(&remaining[..n]);
self.store_data_len += n;
remaining = &remaining[n..];
if self.store_data_len == self.blocksize {
self.emit_store_block()?;
}
}
Ok(buf.len())
}
fn emit_store_block(&mut self) -> io::Result<()> {
let data_len = self.store_data_len;
let data_offset = BGZF_HEADER_SIZE + DEFLATE_STORED_HEADER_SIZE;
let header = header_inner(self.level, (DEFLATE_STORED_HEADER_SIZE + data_len) as u16);
self.compressed_buffer[..BGZF_HEADER_SIZE].copy_from_slice(&header);
let len = data_len as u16;
self.compressed_buffer[BGZF_HEADER_SIZE] = 0b001;
self.compressed_buffer[BGZF_HEADER_SIZE + 1..BGZF_HEADER_SIZE + 3]
.copy_from_slice(&len.to_le_bytes());
self.compressed_buffer[BGZF_HEADER_SIZE + 3..BGZF_HEADER_SIZE + 5]
.copy_from_slice(&(!len).to_le_bytes());
let footer_offset = data_offset + data_len;
self.compressed_buffer[footer_offset..footer_offset + BGZF_SIZEOF_CRC32]
.copy_from_slice(&self.store_crc.sum().to_le_bytes());
self.compressed_buffer[footer_offset + BGZF_SIZEOF_CRC32..footer_offset + BGZF_FOOTER_SIZE]
.copy_from_slice(&(data_len as u32).to_le_bytes());
let end = footer_offset + BGZF_FOOTER_SIZE;
let writer = self
.writer
.as_mut()
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "writer already finished"))?;
writer.write_all(&self.compressed_buffer[..end])?;
self.store_crc = Crc::new();
self.store_data_len = 0;
Ok(())
}
}
impl Writer<File> {
pub fn from_path<P>(path: P, compression_level: CompressionLevel) -> io::Result<Self>
where
P: AsRef<Path>,
{
File::create(path).map(|f| Self::new(f, compression_level))
}
}
impl Writer<BufWriter<File>> {
pub fn from_path_buffered<P>(path: P, compression_level: CompressionLevel) -> io::Result<Self>
where
P: AsRef<Path>,
{
File::create(path)
.map(|f| Self::new(BufWriter::with_capacity(256 * 1024, f), compression_level))
}
}
impl<W> Write for Writer<W>
where
W: Write,
{
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
if self.store_only {
if self.writer.is_none() {
return Err(io::Error::new(io::ErrorKind::Other, "writer already finished"));
}
return self.write_store_only(buf);
}
let writer = self
.writer
.as_mut()
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "writer already finished"))?;
self.uncompressed_buffer.extend_from_slice(buf);
while self.uncompressed_buffer.len() >= self.blocksize {
let b = self.uncompressed_buffer.split_to(self.blocksize).freeze();
self.compressor
.compress(&b[..], &mut self.compressed_buffer)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
writer.write_all(&self.compressed_buffer)?;
self.compressed_buffer.clear();
}
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
self.flush_buffer()?;
if let Some(writer) = self.writer.as_mut() {
writer.flush()?;
}
Ok(())
}
}
impl<W> Drop for Writer<W>
where
W: Write,
{
fn drop(&mut self) {
if self.writer.is_some() {
let _ = self.flush_buffer();
if let Some(ref mut writer) = self.writer {
let _ = writer.write_all(BGZF_EOF);
let _ = writer.flush();
}
}
}
}