use super::Backend;
use crate::error;
use std::fs::OpenOptions;
use std::path::{Path, PathBuf};
use tempfile::NamedTempFile;
#[derive(Debug)]
pub struct PathBackend {
path: PathBuf,
}
impl PathBackend {
pub fn from_path_or_fail(path: PathBuf) -> error::BackendResult<Self> {
OpenOptions::new().read(true).open(path.as_path())?;
Ok(Self { path })
}
pub fn from_path_or_create(path: PathBuf) -> error::BackendResult<(Self, bool)> {
let exists = path.as_path().is_file();
OpenOptions::new()
.write(true)
.create(true)
.open(path.as_path())?;
Ok((Self { path }, exists))
}
pub fn from_path_or_create_and<C>(path: PathBuf, closure: C) -> error::BackendResult<Self>
where
C: FnOnce(&mut std::fs::File),
{
let exists = path.as_path().is_file();
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(path.as_path())?;
if !exists {
closure(&mut file)
}
Ok(Self { path })
}
}
impl Backend for PathBackend {
fn get_data(&mut self) -> error::BackendResult<Vec<u8>> {
use std::io::Read;
let mut file = OpenOptions::new().read(true).open(self.path.as_path())?;
let mut buffer = vec![];
file.read_to_end(&mut buffer)?;
Ok(buffer)
}
fn put_data(&mut self, data: &[u8]) -> error::BackendResult<()> {
use std::io::Write;
#[allow(clippy::or_fun_call)] let mut tempf = NamedTempFile::new_in(self.path.parent().unwrap_or(Path::new(".")))?;
tempf.write_all(data)?;
tempf.as_file().sync_all()?;
tempf.persist(self.path.as_path())?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::{Backend, PathBackend};
use std::io::Write;
use tempfile::NamedTempFile;
#[test]
#[cfg_attr(miri, ignore)]
fn test_path_backend_existing() {
let file = NamedTempFile::new().expect("could not create temporary file");
let (mut backend, existed) = PathBackend::from_path_or_create(file.path().to_owned())
.expect("could not create backend");
assert!(existed);
let data = [4, 5, 1, 6, 8, 1];
backend.put_data(&data).expect("could not put data");
assert_eq!(backend.get_data().expect("could not get data"), data);
}
#[test]
#[cfg_attr(miri, ignore)]
fn test_path_backend_new() {
let dir = tempfile::tempdir().expect("could not create temporary directory");
let mut file_path = dir.path().to_owned();
file_path.push("rustbreak_path_db.db");
let (mut backend, existed) =
PathBackend::from_path_or_create(file_path).expect("could not create backend");
assert!(!existed);
let data = [4, 5, 1, 6, 8, 1];
backend.put_data(&data).expect("could not put data");
assert_eq!(backend.get_data().expect("could not get data"), data);
dir.close().expect("Error while deleting temp directory!");
}
#[test]
#[cfg_attr(miri, ignore)]
fn test_path_backend_nofail() {
let file = NamedTempFile::new().expect("could not create temporary file");
let file_path = file.path().to_owned();
let mut backend = PathBackend::from_path_or_fail(file_path).expect("should not fail");
let data = [4, 5, 1, 6, 8, 1];
backend.put_data(&data).expect("could not put data");
assert_eq!(backend.get_data().expect("could not get data"), data);
}
#[test]
#[cfg_attr(miri, ignore)]
fn test_path_backend_fail_notfound() {
let dir = tempfile::tempdir().expect("could not create temporary directory");
let mut file_path = dir.path().to_owned();
file_path.push("rustbreak_path_db.db");
let err =
PathBackend::from_path_or_fail(file_path).expect_err("should fail with file not found");
if let crate::error::BackendError::Io(io_err) = &err {
assert_eq!(std::io::ErrorKind::NotFound, io_err.kind());
} else {
panic!("Wrong kind of error returned: {}", err);
};
dir.close().expect("Error while deleting temp directory!");
}
#[test]
#[cfg_attr(miri, ignore)]
fn test_path_backend_create_and_existing_nocall() {
let file = NamedTempFile::new().expect("could not create temporary file");
let mut backend = PathBackend::from_path_or_create_and(file.path().to_owned(), |_| {
panic!("Closure called but file already existed");
})
.expect("could not create backend");
let data = [4, 5, 1, 6, 8, 1];
backend.put_data(&data).expect("could not put data");
assert_eq!(backend.get_data().expect("could not get data"), data);
}
#[test]
#[cfg_attr(miri, ignore)]
fn test_path_backend_create_and_new() {
let dir = tempfile::tempdir().expect("could not create temporary directory");
let mut file_path = dir.path().to_owned();
file_path.push("rustbreak_path_db.db");
let mut backend = PathBackend::from_path_or_create_and(file_path, |f| {
f.write_all(b"this is a new file")
.expect("could not write to file")
})
.expect("could not create backend");
assert_eq!(
backend.get_data().expect("could not get data"),
b"this is a new file"
);
let data = [4, 5, 1, 6, 8, 1];
backend.put_data(&data).expect("could not put data");
assert_eq!(backend.get_data().expect("could not get data"), data);
dir.close().expect("Error while deleting temp directory!");
}
}