use std::io::{Cursor, Read};
use flate2::{read::DeflateEncoder, Compression};
use rusqlite::{blob::ZeroBlob, Connection, DatabaseName};
use crate::{decompress::BlockType, errors::CorniferError};
fn dist_in_bits(byte1: usize, bit1: u8, byte2: usize, bit2: u8) -> isize {
let bit2 = bit2 as isize;
let bit1 = bit1 as isize;
let byte1 = byte1 as isize;
let byte2 = byte2 as isize;
((byte2 - byte1) * 8) + (bit2 - bit1)
}
pub struct Checkpointer {
conn: Connection,
emit_block_type: BlockType,
emit_byte: usize,
emit_bit: u8,
to_byte: usize,
current_block_id: i64,
}
fn setup_connection(conn: &Connection) -> Result<(), CorniferError> {
conn.execute(
"
CREATE TABLE DeflateBlock (
id INTEGER PRIMARY KEY AUTOINCREMENT,
from_byte INTEGER NOT NULL,
from_bit INTEGER NOT NULL,
to_byte INTEGER NOT NULL,
block_type TEXT NOT NULL,
crc32 TEXT,
len INTEGER,
header_len_bits INTEGER,
block_len_bits INTEGER,
data BLOB NOT NULL
)",
(),
)?;
Ok(())
}
impl Checkpointer {
pub fn init(path: String) -> Result<Self, CorniferError> {
let conn = Connection::open(path)?;
setup_connection(&conn)?;
Ok(Self {
conn,
emit_block_type: BlockType::NoCompression, emit_byte: 0,
emit_bit: 0,
to_byte: 0,
current_block_id: 0,
})
}
pub fn init_memory() -> Result<Self, CorniferError> {
let conn = Connection::open_in_memory()?;
setup_connection(&conn)?;
Ok(Self {
conn,
emit_block_type: BlockType::NoCompression, emit_byte: 0,
emit_bit: 0,
to_byte: 0,
current_block_id: 0
})
}
pub fn set_block_type(&mut self, block_type: BlockType) {
self.emit_block_type = block_type;
}
pub fn on_block_start(&mut self, curr_byte: usize, bit: u8, to_byte: usize) {
self.emit_byte = if bit == 0 { curr_byte } else { curr_byte - 1 };
self.emit_bit = bit;
self.to_byte = to_byte;
}
pub fn on_block_data_start(
&mut self,
curr_byte: usize,
bit: u8,
data: Vec<u8>,
) -> Result<(), CorniferError> {
let curr_byte = if bit == 0 { curr_byte } else { curr_byte - 1 };
let block_header_size_bits = dist_in_bits(self.emit_byte, self.emit_bit, curr_byte, bit);
let block_type = match self.emit_block_type {
BlockType::NoCompression => "nocompression",
BlockType::FixedHuffman => "fixed",
BlockType::DynamicHuffman => "dynamic",
};
let mut encoder = DeflateEncoder::new(Cursor::new(data), Compression::best());
let mut compressed_data = Vec::new();
encoder.read_to_end(&mut compressed_data)?;
self.conn.execute("
INSERT INTO DeflateBlock (from_byte, from_bit, to_byte, block_type, header_len_bits, data) VALUES (?1, ?2, ?3, ?4, ?5, ?6)
", (self.emit_byte, self.emit_bit, self.to_byte, block_type, block_header_size_bits, ZeroBlob(compressed_data.len().try_into().expect("Max size for data will be 32kb, so this should always fit"))))?;
let rowid = self.conn.last_insert_rowid();
self.current_block_id = rowid;
let mut blob =
self.conn
.blob_open(DatabaseName::Main, "DeflateBlock", "data", rowid, false)?;
let mut file = Cursor::new(compressed_data);
std::io::copy(&mut file, &mut blob)?;
Ok(())
}
pub fn on_block_end(
&mut self,
curr_byte: usize,
bit: u8,
to_byte: usize,
crc32: u32
) -> Result<(), CorniferError> {
let curr_byte = if bit == 0 { curr_byte } else { curr_byte - 1 };
let rowid = self.current_block_id;
let entire_block_size_bits = dist_in_bits( self.emit_byte, self.emit_bit, curr_byte, bit);
let uncompressed_block_size = to_byte - self.to_byte;
let formatted_crc = format!("{crc32:x}");
self.conn.execute("
UPDATE DeflateBlock
SET crc32 = ?1,
len = ?2,
block_len_bits = ?3
WHERE DeflateBlock.id = ?4
", (formatted_crc, uncompressed_block_size, entire_block_size_bits, rowid))?;
Ok(())
}
}