use std::io;
use std::path::{Path, PathBuf};
pub trait SnapshotStore: Send + Sync {
fn load(&self) -> io::Result<Option<Vec<u8>>>;
fn save(&self, bytes: &[u8]) -> io::Result<()>;
}
pub struct MemorySnapshotStore;
impl MemorySnapshotStore {
pub fn new() -> Self {
Self
}
}
impl Default for MemorySnapshotStore {
fn default() -> Self {
Self::new()
}
}
impl SnapshotStore for MemorySnapshotStore {
fn load(&self) -> io::Result<Option<Vec<u8>>> {
Ok(None)
}
fn save(&self, _bytes: &[u8]) -> io::Result<()> {
Ok(())
}
}
pub struct DiskSnapshotStore {
path: PathBuf,
}
impl DiskSnapshotStore {
pub fn new(path: PathBuf) -> Self {
Self { path }
}
pub fn path(&self) -> &Path {
&self.path
}
}
impl SnapshotStore for DiskSnapshotStore {
fn load(&self) -> io::Result<Option<Vec<u8>>> {
match std::fs::read(&self.path) {
Ok(bytes) => Ok(Some(bytes)),
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err),
}
}
fn save(&self, bytes: &[u8]) -> io::Result<()> {
if let Some(parent) = self.path.parent() {
std::fs::create_dir_all(parent)?;
}
crate::atomic::write_atomic_bytes(&self.path, bytes)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn memory_store_is_noop() {
let store = MemorySnapshotStore::new();
assert!(store.load().unwrap().is_none());
store.save(b"anything").unwrap();
assert!(store.load().unwrap().is_none());
}
#[test]
fn disk_store_round_trips() {
let tmp = tempfile::tempdir().unwrap();
let store = DiskSnapshotStore::new(tmp.path().join("sub/dir/snapshot.json"));
assert!(store.load().unwrap().is_none());
store.save(b"hello world").unwrap();
assert_eq!(store.load().unwrap().unwrap(), b"hello world");
store.save(b"second write").unwrap();
assert_eq!(store.load().unwrap().unwrap(), b"second write");
}
}