use std::fs::File;
use std::io::{self, BufReader};
use std::io::{Read, Seek, SeekFrom};
use std::path::Path;
pub struct AlignedReader<R>
where
R: Read + Seek,
{
inner: R,
alignment: u64,
position: u64,
buffer_pos: u64,
buffer_size: usize,
buffer: Vec<u8>,
}
impl<R> AlignedReader<R>
where
R: Read + Seek,
{
pub fn new(inner: R, alignment: u64) -> io::Result<Self> {
if !alignment.is_power_of_two() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"alignment must be a power of two",
));
}
Ok(Self {
inner,
alignment,
position: 0,
buffer_pos: 0,
buffer_size: 0,
buffer: Vec::with_capacity(alignment as usize),
})
}
fn round_down(&self, n: u64) -> u64 {
n / self.alignment * self.alignment
}
fn round_up(&self, n: u64) -> u64 {
if n.is_multiple_of(self.alignment) {
n
} else {
self.round_down(n) + self.alignment
}
}
}
impl<R> Read for AlignedReader<R>
where
R: Read + Seek,
{
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let aligned_position = self.round_down(self.position);
let start = (self.position - aligned_position) as usize;
let to_read = buf.len().min(self.alignment as usize - start);
let end = start + to_read;
let size = self.round_up(end as u64) as usize;
if aligned_position != self.buffer_pos || size > self.buffer_size {
self.inner.seek(SeekFrom::Start(aligned_position))?;
self.buffer.resize(size, 0u8);
self.inner.read_exact(&mut self.buffer)?;
self.buffer_pos = aligned_position;
self.buffer_size = size;
}
buf[..to_read].copy_from_slice(&self.buffer[start..end]);
self.position += to_read as u64;
Ok(to_read)
}
}
impl<R> Seek for AlignedReader<R>
where
R: Read + Seek,
{
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
let raw_position = match pos {
SeekFrom::Start(n) => Some(n),
SeekFrom::End(_) => {
return Err(io::Error::other("unsupported"));
}
SeekFrom::Current(n) => {
if n >= 0 {
self.position.checked_add(n as u64)
} else {
self.position.checked_sub(n.wrapping_neg() as u64)
}
}
};
match raw_position {
Some(n) => {
let aligned_position = self.round_down(n);
self.inner.seek(SeekFrom::Start(aligned_position))?;
self.position = n;
Ok(n)
}
None => Err(io::Error::new(
io::ErrorKind::InvalidInput,
"invalid position",
)),
}
}
}
pub fn open_volume(path: &Path) -> std::io::Result<BufReader<AlignedReader<File>>> {
let file = File::open(path)?;
let sr = AlignedReader::new(file, 4096u64)?;
let mut reader = BufReader::new(sr);
reader.seek(SeekFrom::Start(0))?;
Ok(reader)
}