use crate::error::{self, Result};
use chrono::{DateTime, Utc};
use log::debug;
use serde::Serialize;
use snafu::{ensure, ResultExt};
use std::io::ErrorKind;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tempfile::TempDir;
use tokio::sync::{Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard};
#[derive(Debug, Clone)]
pub(crate) struct Datastore {
path_lock: Arc<RwLock<DatastorePath>>,
time_lock: Arc<Mutex<()>>,
}
impl Datastore {
pub(crate) fn new(path: Option<PathBuf>) -> Result<Self> {
Ok(Self {
path_lock: Arc::new(RwLock::new(match path {
None => DatastorePath::TempDir(TempDir::new().context(error::DatastoreInitSnafu)?),
Some(p) => DatastorePath::Path(p),
})),
time_lock: Arc::new(Mutex::new(())),
})
}
async fn read(&self) -> RwLockReadGuard<'_, DatastorePath> {
self.path_lock.read().await
}
async fn write(&self) -> RwLockWriteGuard<'_, DatastorePath> {
self.path_lock.write().await
}
pub(crate) async fn bytes(&self, file: &str) -> Result<Option<Vec<u8>>> {
let lock = &self.read().await;
let path = lock.path().join(file);
match tokio::fs::read(&path).await {
Ok(file) => Ok(Some(file)),
Err(err) => match err.kind() {
ErrorKind::NotFound => Ok(None),
_ => Err(err).context(error::DatastoreOpenSnafu { path: &path }),
},
}
}
pub(crate) async fn create<T: Serialize>(&self, file: &str, value: &T) -> Result<()> {
let lock = &self.write().await;
let path = lock.path().join(file);
let bytes = serde_json::to_vec(value).with_context(|_| error::DatastoreSerializeSnafu {
what: format!("{file} in datastore"),
path: path.clone(),
})?;
tokio::fs::write(&path, bytes)
.await
.context(error::DatastoreCreateSnafu { path: &path })
}
pub(crate) async fn remove(&self, file: &str) -> Result<()> {
let lock = self.write().await;
let path = lock.path().join(file);
debug!("removing '{}'", path.display());
match tokio::fs::remove_file(&path).await {
Ok(()) => Ok(()),
Err(err) => match err.kind() {
ErrorKind::NotFound => Ok(()),
_ => Err(err).context(error::DatastoreRemoveSnafu { path: &path }),
},
}
}
pub(crate) async fn system_time(&self) -> Result<DateTime<Utc>> {
let lock = self.time_lock.lock().await;
let file = "latest_known_time.json";
let poss_latest_known_time = self
.bytes(file)
.await?
.map(|b| serde_json::from_slice::<DateTime<Utc>>(&b));
let sys_time = Utc::now();
if let Some(Ok(latest_known_time)) = poss_latest_known_time {
ensure!(
sys_time >= latest_known_time,
error::SystemTimeSteppedBackwardSnafu {
sys_time,
latest_known_time
}
);
}
self.create(file, &sys_time).await?;
drop(lock);
Ok(sys_time)
}
}
#[derive(Debug)]
enum DatastorePath {
Path(PathBuf),
TempDir(TempDir),
}
impl DatastorePath {
fn path(&self) -> &Path {
match self {
DatastorePath::Path(p) => p,
DatastorePath::TempDir(t) => t.path(),
}
}
}