use std::{
fs::File,
io::Seek,
ops::{Deref, DerefMut},
path::Path,
};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::inode_aware::{InodeAwareOffset, InodeAwareReader};
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct State {
pub offset: InodeAwareOffset,
}
#[derive(Error, Debug)]
pub enum StateSerdeError {
#[error("while working with underlying file")]
IO(#[from] std::io::Error),
#[error("while trying to (de)serialize state")]
Serde(#[from] bincode::Error),
}
impl State {
pub fn load(file: &mut File) -> Result<Self, StateSerdeError> {
file.rewind()?;
let state = bincode::deserialize_from(file)?;
Ok(state)
}
pub fn persist(&self, file: &mut File) -> std::io::Result<()> {
file.rewind()?;
match bincode::serialize_into(file, self) {
Ok(_) => {}
Err(e) => match *e {
bincode::ErrorKind::Io(ioerr) => return Err(ioerr),
_ => unreachable!(),
},
}
Ok(())
}
}
pub struct TrackedReader {
inner: InodeAwareReader,
registry: File,
already_freed: bool,
}
#[derive(Error, Debug)]
pub enum TrackedReaderError {
#[error("while working with underlying file")]
IO(#[from] std::io::Error),
#[error("while working with persistent state storage")]
Persistence(#[from] StateSerdeError),
#[error("trying to resolve logrotated file")]
RotationResolution(String),
}
impl TrackedReader {
pub fn new(
filepath: impl AsRef<Path>,
registry: impl AsRef<Path>,
) -> Result<Self, TrackedReaderError> {
Self::with_search_depth(filepath, registry, 1)
}
pub fn with_search_depth(
filepath: impl AsRef<Path>,
registry: impl AsRef<Path>,
search_depth: usize,
) -> Result<Self, TrackedReaderError> {
let state_from_disk = maybe_read_state(registry.as_ref())?;
let reader = InodeAwareReader::from_rotated_logs_with_depth(filepath, search_depth)?;
let registry = open_state_file(registry)?;
let mut reader = Self {
inner: reader,
registry,
already_freed: false,
};
if let Some(state) = state_from_disk {
reader.seek_persistent(state.offset)?;
} else {
reader.persist()?;
}
Ok(reader)
}
pub fn persist(&mut self) -> std::io::Result<()> {
self.get_persistent_state().persist(&mut self.registry)
}
pub fn close(mut self) -> std::io::Result<()> {
self.persist()?;
self.already_freed = true;
Ok(())
}
pub fn get_persistent_state(&self) -> State {
State {
offset: self.get_persistent_offset(),
}
}
}
fn maybe_read_state(path: &Path) -> Result<Option<State>, TrackedReaderError> {
if !path.exists() {
return Ok(None);
}
let mut file = File::options().read(true).open(path)?;
let state = State::load(&mut file)?;
Ok(Some(state))
}
fn open_state_file(path: impl AsRef<Path>) -> std::io::Result<File> {
File::options()
.read(true)
.write(true)
.create(true)
.open(path)
}
impl Deref for TrackedReader {
type Target = InodeAwareReader;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl DerefMut for TrackedReader {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
impl Drop for TrackedReader {
fn drop(&mut self) {
if !self.already_freed {
self.persist().unwrap()
}
}
}