use std::marker::PhantomData;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use serde::de::DeserializeOwned;
use serde::Serialize;
use onevcs::{Error, Result};
pub trait Checked {
fn check(&self) -> Result<()>;
}
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()
),
})?;
std::fs::write(&self.path, format!("{json}\n")).map_err(|e| 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 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()),
})?;
Ok(state)
}
}