use std::fs::{File, OpenOptions};
use std::io::{BufReader, BufWriter, Read, Seek, SeekFrom, Write};
use std::path::Path;
const DEFAULT_BUF_SIZE: usize = 64 * 1024;
pub struct FileHandle {
file: Option<File>,
mode: Mode,
}
enum Mode {
Writer(BufWriter<File>),
Reader(BufReader<File>),
ReadOnly(BufReader<File>),
Transitioning,
}
impl FileHandle {
pub fn create(path: &Path) -> std::io::Result<Self> {
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(path)?;
Ok(Self {
file: None,
mode: Mode::Writer(BufWriter::with_capacity(DEFAULT_BUF_SIZE, file)),
})
}
pub fn open_read(path: &Path) -> std::io::Result<Self> {
let file = OpenOptions::new().read(true).open(path)?;
Ok(Self {
file: None,
mode: Mode::ReadOnly(BufReader::with_capacity(DEFAULT_BUF_SIZE, file)),
})
}
pub fn open_readwrite(path: &Path) -> std::io::Result<Self> {
let file = OpenOptions::new().read(true).write(true).open(path)?;
Ok(Self {
file: None,
mode: Mode::Writer(BufWriter::with_capacity(DEFAULT_BUF_SIZE, file)),
})
}
fn take_file(&mut self) -> std::io::Result<File> {
let old = std::mem::replace(&mut self.mode, Mode::Transitioning);
match old {
Mode::Writer(w) => w.into_inner().map_err(|e| e.into_error()),
Mode::Reader(r) => Ok(r.into_inner()),
Mode::ReadOnly(r) => Ok(r.into_inner()),
Mode::Transitioning => {
self.file
.take()
.ok_or_else(|| std::io::Error::other("no file available"))
}
}
}
fn ensure_writer(&mut self) -> std::io::Result<()> {
match &self.mode {
Mode::Writer(_) => return Ok(()),
Mode::ReadOnly(_) => {
return Err(std::io::Error::new(
std::io::ErrorKind::PermissionDenied,
"file opened read-only",
));
}
_ => {}
}
let file = self.take_file()?;
self.mode = Mode::Writer(BufWriter::with_capacity(DEFAULT_BUF_SIZE, file));
Ok(())
}
fn ensure_reader(&mut self) -> std::io::Result<()> {
match &self.mode {
Mode::Reader(_) | Mode::ReadOnly(_) => return Ok(()),
_ => {}
}
let file = self.take_file()?;
self.mode = Mode::Reader(BufReader::with_capacity(DEFAULT_BUF_SIZE, file));
Ok(())
}
pub fn write_at(&mut self, offset: u64, data: &[u8]) -> std::io::Result<()> {
self.ensure_writer()?;
if let Mode::Writer(w) = &mut self.mode {
w.seek(SeekFrom::Start(offset))?;
w.write_all(data)?;
}
Ok(())
}
pub fn read_at(&mut self, offset: u64, len: usize) -> std::io::Result<Vec<u8>> {
self.ensure_reader()?;
match &mut self.mode {
Mode::Reader(r) | Mode::ReadOnly(r) => {
r.seek(SeekFrom::Start(offset))?;
let mut buf = vec![0u8; len];
r.read_exact(&mut buf)?;
Ok(buf)
}
_ => unreachable!(),
}
}
pub fn read_at_most(&mut self, offset: u64, max_len: usize) -> std::io::Result<Vec<u8>> {
self.ensure_reader()?;
match &mut self.mode {
Mode::Reader(r) | Mode::ReadOnly(r) => {
r.seek(SeekFrom::Start(offset))?;
let mut buf = vec![0u8; max_len];
let mut total = 0;
loop {
match r.read(&mut buf[total..]) {
Ok(0) => break,
Ok(n) => total += n,
Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
}
}
buf.truncate(total);
Ok(buf)
}
_ => unreachable!(),
}
}
pub fn sync_data(&mut self) -> std::io::Result<()> {
self.flush_buffers()?;
self.get_file_ref().sync_data()
}
pub fn sync_all(&mut self) -> std::io::Result<()> {
self.flush_buffers()?;
self.get_file_ref().sync_all()
}
pub fn file_size(&mut self) -> std::io::Result<u64> {
self.ensure_reader()?;
match &mut self.mode {
Mode::Reader(r) | Mode::ReadOnly(r) => {
let pos = r.seek(SeekFrom::End(0))?;
Ok(pos)
}
_ => unreachable!(),
}
}
fn flush_buffers(&mut self) -> std::io::Result<()> {
if let Mode::Writer(w) = &mut self.mode {
w.flush()?;
}
Ok(())
}
fn get_file_ref(&self) -> &File {
match &self.mode {
Mode::Writer(w) => w.get_ref(),
Mode::Reader(r) | Mode::ReadOnly(r) => r.get_ref(),
Mode::Transitioning => unreachable!("sync during transition"),
}
}
}
#[cfg(feature = "mmap")]
pub struct MmapFileHandle {
mmap: memmap2::Mmap,
}
#[cfg(feature = "mmap")]
impl MmapFileHandle {
pub fn open(path: &Path) -> std::io::Result<Self> {
let file = File::open(path)?;
let mmap = unsafe { memmap2::Mmap::map(&file)? };
Ok(Self { mmap })
}
pub fn len(&self) -> usize {
self.mmap.len()
}
pub fn is_empty(&self) -> bool {
self.mmap.is_empty()
}
pub fn read_at(&self, offset: u64, len: usize) -> std::io::Result<&[u8]> {
let start = offset as usize;
let end = start + len;
if end > self.mmap.len() {
return Err(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
format!(
"mmap read past end: offset={} len={} file_size={}",
offset,
len,
self.mmap.len()
),
));
}
Ok(&self.mmap[start..end])
}
pub fn read_at_most(&self, offset: u64, max_len: usize) -> &[u8] {
let start = offset as usize;
let end = std::cmp::min(start + max_len, self.mmap.len());
if start >= self.mmap.len() {
return &[];
}
&self.mmap[start..end]
}
}