use std::{
fs::{File, OpenOptions},
io::{Read, Write},
path::{Path, PathBuf},
};
use super::{Clock, mem::MemClock};
#[derive(Debug)]
pub struct PersistedClock {
clock: MemClock,
file_path: PathBuf,
}
impl Clock for PersistedClock {
type IncrementError = write::Error;
type WitnessError = write::Error;
fn time(&self) -> super::Time {
self.clock.time()
}
fn increment(&mut self) -> Result<super::Time, Self::IncrementError> {
let previous = self.clock.increment().expect("This error is infallible");
self.save_to_file(false).map(|()| previous)
}
fn witness(&mut self, time: super::Time) -> Result<(), Self::WitnessError> {
self.clock.witness(time).expect("The error is infallible");
self.save_to_file(false)
}
}
pub mod write {
#![allow(missing_docs)]
use std::{io, num::ParseIntError, path::PathBuf};
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Failed to write this clock to {path}, because of {error}")]
Write { path: PathBuf, error: io::Error },
#[error("Failed to open the path '{path}' for clock, because of {error}")]
Open { path: PathBuf, error: io::Error },
#[error("Failed to read the contents of the path {path} for clock, because of {error}")]
Read { path: PathBuf, error: io::Error },
#[error(
"Failed to parse the contents ('{contents}') of the path {path} as clock value, \
because of {error}"
)]
Parse {
path: PathBuf,
error: ParseIntError,
contents: String,
},
}
}
impl PersistedClock {
fn open_file(path: &Path, options: &mut OpenOptions) -> Result<File, write::Error> {
options.open(path).map_err(|err| write::Error::Open {
error: err,
path: path.to_owned(),
})
}
pub fn new(path: PathBuf) -> Result<Self, write::Error> {
let mut me = Self {
clock: MemClock::new(),
file_path: path,
};
me.save_to_file(true)?;
Ok(me)
}
pub fn from_path(path: PathBuf) -> Result<Self, write::Error> {
let me = Self {
clock: MemClock::new_with_value(Self::time_from_file(&path)?),
file_path: path,
};
Ok(me)
}
fn time_from_file(path: &Path) -> Result<u64, write::Error> {
let mut file = Self::open_file(path, OpenOptions::new().read(true))?;
let mut contents = String::new();
file.read_to_string(&mut contents)
.map_err(|err| write::Error::Read {
error: err,
path: path.to_owned(),
})?;
let value: u64 = contents.parse().map_err(|err| write::Error::Parse {
error: err,
path: path.to_owned(),
contents,
})?;
Ok(value)
}
pub fn save_to_file(&mut self, create: bool) -> Result<(), write::Error> {
if self.file_path.exists() {
assert!(
Self::time_from_file(&self.file_path)? <= self.time().0,
"The time should have not been changed under our feet."
);
}
let mut file = Self::open_file(
&self.file_path,
OpenOptions::new().write(true).create(create),
)?;
write!(file, "{}", self.time().0).map_err(|err| write::Error::Write {
error: err,
path: self.file_path.clone(),
})
}
}