#![doc = include_str!("../README.md")]
use std::{
fs,
io::{self, ErrorKind},
marker::PhantomData,
path::{Path, PathBuf},
};
use serde::{de::DeserializeOwned, Serialize};
use thiserror::Error;
const DEFAULT_FILE_TYPE: &'static str = "persist";
pub struct DiskPersist<D> {
path: PathBuf,
_type: PhantomData<D>,
}
impl<D: Serialize + DeserializeOwned> DiskPersist<D> {
pub fn init(name: impl AsRef<str>) -> Result<Self, DataError> {
let mut path: PathBuf = match dirs::cache_dir() {
Some(base) => base,
None => return Err(DataError::NoCacheDir),
};
path.push(&format!("{}.{}", name.as_ref(), DEFAULT_FILE_TYPE));
Ok(Self {
_type: PhantomData::default(),
path,
})
}
pub fn init_with_path(path: impl AsRef<Path>) -> Result<Self, DataError> {
let path = path.as_ref().to_path_buf();
if path.is_dir() {
return Err(DataError::FoundDirectory);
}
Ok(Self {
_type: PhantomData::default(),
path,
})
}
pub fn write(&self, data: &D) -> Result<(), DataError> {
let bytes = match bincode::serialize(data) {
Ok(bytes) => bytes,
Err(error) => return Err(DataError::Serde(error)),
};
match fs::write(&self.path, bytes) {
Ok(_) => Ok(()),
Err(error) => return Err(DataError::Io(error)),
}
}
pub fn read(&self) -> Result<Option<D>, DataError> {
let bytes = match fs::read(&self.path) {
Ok(bytes) => bytes,
Err(error) => {
return match error.kind() {
ErrorKind::NotFound => return Ok(None),
_ => Err(DataError::Io(error)),
}
}
};
let deserialized: D = match bincode::deserialize(&bytes) {
Ok(deserialized) => deserialized,
Err(error) => return Err(DataError::Serde(error)),
};
Ok(Some(deserialized))
}
pub fn path(&self) -> &Path {
self.path.as_path()
}
}
#[derive(Error, Debug)]
pub enum DataError {
#[error(transparent)]
Io(#[from] io::Error),
#[error(transparent)]
Serde(#[from] bincode::Error),
#[error("couldn't find cache directory")]
NoCacheDir,
#[error("optional save path must be a file, not a directory")]
FoundDirectory,
}
#[cfg(test)]
mod tests {
use std::fs;
use serde::{Deserialize, Serialize};
use crate::DiskPersist;
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Data {
name: String,
age: u8,
location: (f64, f64),
}
impl Default for Data {
fn default() -> Self {
Self {
name: "Jane Doe".to_string(),
age: 45,
location: (49.24565431256531, 111.35598566896671),
}
}
}
#[test]
fn full_test() {
let name = "disk-persist-test";
let write: DiskPersist<Data> = DiskPersist::init(name).unwrap();
let write_data = Data::default();
write.write(&write_data).unwrap();
let read: DiskPersist<Data> = DiskPersist::init(name).unwrap();
let read_data = read.read().unwrap().unwrap();
assert_eq!(write_data, read_data);
fs::remove_file(write.path()).unwrap();
}
}