use std::{
fs::File,
io::{self, BufWriter, Seek, SeekFrom, Write},
marker::PhantomData,
path::Path,
};
use crate::file::{FileWriter, binary::SizedChunk};
pub struct BinaryWriter<T>
where
T: WriteChunk,
{
file: BufWriter<File>,
buf: Vec<u8>,
count: u64,
_marker: PhantomData<T>,
}
pub trait WriteChunk: SizedChunk {
fn as_chunk(&self, buf: &mut Vec<u8>) -> io::Result<()>;
}
impl<T> FileWriter for BinaryWriter<T>
where
T: WriteChunk,
{
fn from_path<P>(path: P) -> io::Result<Self>
where
Self: Sized,
P: AsRef<Path>,
{
BinaryWriter::from_file(File::create(path)?)
}
fn from_file(file: File) -> io::Result<Self>
where
Self: Sized,
{
let writer = BinaryWriter {
file: BufWriter::new(file),
buf: Vec::<u8>::with_capacity(T::chunk_size()),
count: 0,
_marker: PhantomData,
};
Ok(writer)
}
fn flush(&mut self) -> io::Result<()> {
self.file.flush()
}
}
impl<T> BinaryWriter<T>
where
T: WriteChunk,
{
#[inline]
pub fn write_chunk(&mut self, object: &T) -> io::Result<()> {
self.buf.clear();
self.count += 1;
object.as_chunk(&mut self.buf)?;
if self.buf.len() != T::chunk_size() {
let message = format!("Invalid chunk size at chunk {}", self.count);
return Err(io::Error::new(io::ErrorKind::InvalidData, message));
}
self.file.write_all(&self.buf)
}
}
impl<T> Seek for BinaryWriter<T>
where
T: WriteChunk,
{
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
self.file.seek(pos)
}
}
impl<T> WriteChunk for Option<T>
where
T: WriteChunk,
{
fn as_chunk(&self, buf: &mut Vec<u8>) -> io::Result<()> {
self.is_some().as_chunk(buf)?;
match self {
Some(value) => value.as_chunk(buf)?,
None => {
let size = T::chunk_size();
let zeros = std::iter::repeat_n(0, size);
buf.extend(zeros);
},
};
Ok(())
}
}
impl<T, E> WriteChunk for Result<T, E>
where
T: WriteChunk,
E: WriteChunk,
{
fn as_chunk(&self, buf: &mut Vec<u8>) -> io::Result<()> {
self.is_ok().as_chunk(buf)?;
match self {
Ok(value) => {
value.as_chunk(buf)?;
if T::chunk_size() > E::chunk_size() {
let zeros = std::iter::repeat_n(0, T::chunk_size() - E::chunk_size());
buf.extend(zeros);
}
},
Err(err) => {
err.as_chunk(buf)?;
if E::chunk_size() > T::chunk_size() {
let zeros = std::iter::repeat_n(0, E::chunk_size() - T::chunk_size());
buf.extend(zeros);
}
},
}
Ok(())
}
}
macro_rules! impl_write_chunk_primitive {
(char) => {
impl WriteChunk for char {
#[inline]
fn as_chunk(&self, buf: &mut Vec<u8>) -> io::Result<()> {
let byte = *self as u8;
byte.as_chunk(buf)
}
}
};
(bool) => {
impl WriteChunk for bool {
#[inline]
fn as_chunk(&self, buf: &mut Vec<u8>) -> io::Result<()> {
let byte: u8 = if *self { 1 } else { 0 };
byte.as_chunk(buf)
}
}
};
($T:ty) => {
impl WriteChunk for $T {
#[inline]
fn as_chunk(&self, buf: &mut Vec<u8>) -> io::Result<()> {
buf.extend_from_slice(&self.to_le_bytes());
Ok(())
}
}
};
}
impl_write_chunk_primitive!(u8);
impl_write_chunk_primitive!(i8);
impl_write_chunk_primitive!(u16);
impl_write_chunk_primitive!(i16);
impl_write_chunk_primitive!(u32);
impl_write_chunk_primitive!(i32);
impl_write_chunk_primitive!(u64);
impl_write_chunk_primitive!(i64);
impl_write_chunk_primitive!(u128);
impl_write_chunk_primitive!(i128);
impl_write_chunk_primitive!(usize);
impl_write_chunk_primitive!(isize);
impl_write_chunk_primitive!(f32);
impl_write_chunk_primitive!(f64);
impl_write_chunk_primitive!(char);
impl_write_chunk_primitive!(bool);