use std::marker::PhantomData;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use serde::de::DeserializeOwned;
use serde::Serialize;
use onevcs::{Error, Result};
pub trait Checked {
fn check(&self) -> Result<()>;
fn carry_forward(&mut self);
}
pub trait Store<S> {
fn with<R, F>(&self, act: F) -> Result<R>
where
F: FnOnce(&mut S) -> Result<R>;
fn snapshot(&self) -> Result<S>;
}
#[derive(Debug)]
pub struct MemoryStore<S>(Arc<Mutex<S>>);
impl<S> MemoryStore<S> {
pub fn new(state: S) -> Self {
Self(Arc::new(Mutex::new(state)))
}
}
impl<S> Clone for MemoryStore<S> {
fn clone(&self) -> Self {
Self(Arc::clone(&self.0))
}
}
impl<S: Clone> Store<S> for MemoryStore<S> {
fn with<R, F>(&self, act: F) -> Result<R>
where
F: FnOnce(&mut S) -> Result<R>,
{
let mut guard = self
.0
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
act(&mut guard)
}
fn snapshot(&self) -> Result<S> {
let guard = self
.0
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
Ok(guard.clone())
}
}
#[derive(Debug)]
pub struct FileStore<S> {
path: PathBuf,
marker: PhantomData<S>,
}
impl<S> Clone for FileStore<S> {
fn clone(&self) -> Self {
Self {
path: self.path.clone(),
marker: PhantomData,
}
}
}
impl<S: Serialize + DeserializeOwned + Checked> FileStore<S> {
pub fn attach(path: impl Into<PathBuf>, fallback: &S) -> Result<Self> {
let store = Self::at(path)?;
if store.path.exists() {
store.snapshot()?;
return Ok(store);
}
store.save(fallback)?;
Ok(store)
}
pub fn replace(path: impl Into<PathBuf>, state: &S) -> Result<Self> {
let store = Self::at(path)?;
store.save(state)?;
Ok(store)
}
fn at(path: impl Into<PathBuf>) -> Result<Self> {
let path = path.into();
if let Some(parent) = path
.parent()
.filter(|parent| !parent.as_os_str().is_empty())
{
std::fs::create_dir_all(parent).map_err(|e| Error::Invalid {
reason: format!("cannot create {}: {e}", parent.display()),
})?;
}
Ok(Self {
path,
marker: PhantomData,
})
}
pub fn path(&self) -> &Path {
&self.path
}
fn save(&self, state: &S) -> Result<()> {
let json = serde_json::to_string_pretty(state).map_err(|e| Error::Invalid {
reason: format!(
"cannot serialize the state for {}: {e}",
self.path.display()
),
})?;
static SAVES: AtomicU64 = AtomicU64::new(0);
let mut name = self.path.file_name().unwrap_or_default().to_os_string();
name.push(format!(
".{}.{}.tmp",
std::process::id(),
SAVES.fetch_add(1, Ordering::Relaxed)
));
let staged = self.path.with_file_name(name);
std::fs::write(&staged, format!("{json}\n"))
.and_then(|()| std::fs::rename(&staged, &self.path))
.map_err(|e| {
let _ = std::fs::remove_file(&staged);
Error::Invalid {
reason: format!("cannot write {}: {e}", self.path.display()),
}
})
}
}
impl<S: Serialize + DeserializeOwned + Checked> Store<S> for FileStore<S> {
fn with<R, F>(&self, act: F) -> Result<R>
where
F: FnOnce(&mut S) -> Result<R>,
{
let mut state = self.snapshot()?;
let outcome = act(&mut state)?;
self.save(&state)?;
Ok(outcome)
}
fn snapshot(&self) -> Result<S> {
let raw = std::fs::read_to_string(&self.path).map_err(|e| Error::Invalid {
reason: format!(
"cannot read the provider state at {}: {e}",
self.path.display()
),
})?;
let mut state: S = serde_json::from_str(&raw).map_err(|e| Error::Invalid {
reason: format!(
"the provider state at {} is not the shape this crate writes: {e}",
self.path.display()
),
})?;
state.check().map_err(|e| Error::Invalid {
reason: format!("the provider state at {}: {e}", self.path.display()),
})?;
state.carry_forward();
Ok(state)
}
}