use std::fs::{File, OpenOptions};
use std::io::{Read, Seek, SeekFrom, Write};
use std::path::Path;
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use crate::error::{MulReaderError, MulReaderResult, MulWriterResult};
const UNDEF_RECORD: u32 = 0xFEFEFEFF;
const INDEX_SIZE: u32 = 12;
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct MulRecord {
pub data: Vec<u8>,
pub start: u32,
pub length: u32,
pub opt1: u16,
pub opt2: u16,
}
#[derive(Debug)]
pub struct MulReader<T: Read + Seek> {
idx_reader: T,
data_reader: T,
}
impl MulReader<File> {
pub fn new(idx_path: &Path, mul_path: &Path) -> MulReaderResult<MulReader<File>> {
let idx_reader = File::open(idx_path)?;
let data_reader = File::open(mul_path)?;
Ok(MulReader {
idx_reader,
data_reader,
})
}
}
impl<T: Read + Seek> MulReader<T> {
pub fn from_readables(idx_reader: T, data_reader: T) -> MulReader<T> {
MulReader {
idx_reader,
data_reader,
}
}
pub fn read(&mut self, index: u32) -> MulReaderResult<MulRecord> {
self.idx_reader
.seek(SeekFrom::Start((index * INDEX_SIZE) as u64))?;
let start = self.idx_reader.read_u32::<LittleEndian>()?;
if start == UNDEF_RECORD || start == u32::MAX {
return Err(MulReaderError::OffsetOutOfBounds {
index,
offset: start,
});
}
let length = self.idx_reader.read_u32::<LittleEndian>()?;
let mut data = vec![0; length as usize];
let opt1 = self.idx_reader.read_u16::<LittleEndian>()?;
let opt2 = self.idx_reader.read_u16::<LittleEndian>()?;
self.data_reader.seek(SeekFrom::Start(start as u64))?;
self.data_reader.read_exact(data.as_mut_slice())?;
Ok(MulRecord {
data,
start,
length,
opt1,
opt2,
})
}
}
#[derive(Debug)]
pub struct MulWriter<T: Write + Seek> {
idx_writer: T,
data_writer: T,
}
pub enum MulWriterMode {
Append,
Truncate,
}
impl MulWriter<File> {
pub fn new(
idx_path: &Path,
mul_path: &Path,
mode: MulWriterMode,
) -> MulWriterResult<MulWriter<File>> {
let mut options = OpenOptions::new();
let options = options.write(true).create(true).truncate(match mode {
MulWriterMode::Append => false,
MulWriterMode::Truncate => true,
});
let idx_writer = options.open(idx_path)?;
let data_writer = options.open(mul_path)?;
Ok(MulWriter {
idx_writer,
data_writer,
})
}
}
impl<T: Write + Seek> MulWriter<T> {
pub fn from_writables(idx_writer: T, data_writer: T) -> MulWriter<T> {
MulWriter {
idx_writer,
data_writer,
}
}
pub fn append(
&mut self,
data: &[u8],
opt1: Option<u16>,
opt2: Option<u16>,
) -> MulWriterResult<()> {
self.idx_writer.seek(SeekFrom::End(0))?;
let mul_size = self.data_writer.seek(SeekFrom::End(0))?;
let start = mul_size as u32;
let length = data.len() as u32;
self.data_writer.write_all(data)?;
self.idx_writer.write_u32::<LittleEndian>(start)?;
self.idx_writer.write_u32::<LittleEndian>(length)?;
self.idx_writer
.write_u16::<LittleEndian>(opt1.unwrap_or_default())?;
self.idx_writer
.write_u16::<LittleEndian>(opt2.unwrap_or_default())?;
Ok(())
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use std::io::Cursor;
pub fn simple_from_vecs(vectors: Vec<(Vec<u8>, u16, u16)>) -> MulReader<Cursor<Vec<u8>>> {
let mut idx_reader = Cursor::new(vec![]);
let mut mul_reader = Cursor::new(vec![]);
for (vec, opt1, opt2) in vectors {
let len = vec.len();
let mul_size = mul_reader.seek(SeekFrom::End(0)).unwrap();
let mut idx_cursor = Cursor::new(vec![]);
idx_cursor
.write_u32::<LittleEndian>(mul_size as u32)
.unwrap(); idx_cursor.write_u32::<LittleEndian>(len as u32).unwrap(); idx_cursor.write_u16::<LittleEndian>(opt1).unwrap(); idx_cursor.write_u16::<LittleEndian>(opt2).unwrap(); idx_reader.write_all(idx_cursor.get_ref()).unwrap();
mul_reader.write_all(&vec).unwrap();
}
MulReader::from_readables(idx_reader, mul_reader)
}
pub fn simple_from_mul_records(records: Vec<MulRecord>) -> MulReader<Cursor<Vec<u8>>> {
let mut idx_reader = Cursor::new(vec![]);
let mut mul_reader = Cursor::new(vec![]);
for record in records {
let mul_size = mul_reader.seek(SeekFrom::End(0)).unwrap();
let mut idx_cursor = Cursor::new(vec![]);
idx_cursor
.write_u32::<LittleEndian>(mul_size as u32)
.unwrap(); idx_cursor
.write_u32::<LittleEndian>(record.data.len() as u32)
.unwrap(); idx_cursor.write_u16::<LittleEndian>(record.opt1).unwrap(); idx_cursor.write_u16::<LittleEndian>(record.opt2).unwrap(); idx_reader.write_all(idx_cursor.get_ref()).unwrap();
mul_reader.write_all(&record.data).unwrap();
}
MulReader::from_readables(idx_reader, mul_reader)
}
}