Merge pull request #69 from niluxv/atomic_saves

Add `PathBackend` with atomic saves
This commit is contained in:
Marcel Müller
2020-01-27 14:04:57 +01:00
committed by GitHub
4 changed files with 105 additions and 3 deletions
+1 -1
View File
@@ -16,6 +16,7 @@ all-features = true
[dependencies]
failure = "0.1.1"
serde = "1"
tempfile = "3"
[dependencies.ron]
optional = true
@@ -40,7 +41,6 @@ version = "0.6"
[dev-dependencies]
lazy_static = "1"
serde_derive = "1"
tempfile = "3"
[features]
default = []
+3
View File
@@ -30,6 +30,9 @@ mod mmap;
#[cfg(feature = "mmap")]
pub use self::mmap::MmapStorage;
mod path;
pub use self::path::PathBackend;
/// A backend using a file
#[derive(Debug)]
pub struct FileBackend(::std::fs::File);
+77
View File
@@ -0,0 +1,77 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! Module which implements the [`PathBackend`], storing data in a file on the
//! file system (with a path) and featuring atomic saves.
use super::Backend;
use ::error;
use ::error::RustbreakErrorKind as ErrorKind;
use std::fs::OpenOptions;
use std::path::{Path, PathBuf};
use failure::ResultExt;
use tempfile::NamedTempFile;
/// A [`Backend`] using a file given the path.
///
/// Features atomic saves, so that the database file won't be corrupted or
/// deleted if the program panics during the save.
#[derive(Debug)]
pub struct PathBackend {
path: PathBuf,
}
impl PathBackend {
/// Opens a new [`PathBackend`] for a given path.
pub fn open(path: PathBuf) -> error::Result<Self> {
OpenOptions::new().write(true).create(true).open(path.as_path())
.context(ErrorKind::Backend)?;
Ok(Self {path})
}
}
impl Backend for PathBackend {
fn get_data(&mut self) -> error::Result<Vec<u8>> {
use ::std::io::Read;
let mut file = OpenOptions::new().read(true)
.open(self.path.as_path()).context(ErrorKind::Backend)?;
let mut buffer = vec![];
file.read_to_end(&mut buffer).context(ErrorKind::Backend)?;
Ok(buffer)
}
/// Write the byte slice to the backend. This uses and atomic save.
///
/// This won't corrupt the existing database file if the program panics
/// during the save.
fn put_data(&mut self, data: &[u8]) -> error::Result<()> {
use ::std::io::Write;
let mut tempf = NamedTempFile::new_in(self.path.parent().unwrap_or(Path::new(".")))
.context(ErrorKind::Backend)?;
tempf.write_all(data).context(ErrorKind::Backend)?;
tempf.as_file().sync_all().context(ErrorKind::Backend)?;
tempf.persist(self.path.as_path()).context(ErrorKind::Backend)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use tempfile::NamedTempFile;
use super::{Backend, PathBackend};
#[test]
fn test_path_backend() {
let file = NamedTempFile::new()
.expect("could not create temporary file");
let mut backend = PathBackend::open(file.path().to_owned())
.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);
}
}
+24 -2
View File
@@ -199,7 +199,6 @@ extern crate base64;
#[cfg(feature = "mmap")]
extern crate memmap;
#[cfg(test)]
extern crate tempfile;
/// The rustbreak errors that can be returned
@@ -220,7 +219,7 @@ use serde::Serialize;
use serde::de::DeserializeOwned;
use failure::ResultExt;
use backend::{Backend, MemoryBackend, FileBackend};
use backend::{Backend, MemoryBackend, FileBackend, PathBackend};
#[cfg(feature = "mmap")]
use backend::MmapStorage;
@@ -671,6 +670,29 @@ impl<Data, DeSer> Database<Data, FileBackend, DeSer>
}
}
/// A database backed by a file, using atomic saves.
pub type PathDatabase<D, DS> = Database<D, PathBackend, DS>;
impl<Data, DeSer> Database<Data, PathBackend, DeSer>
where
Data: Serialize + DeserializeOwned + Debug + Clone + Send,
DeSer: DeSerializer<Data> + Debug + Send + Sync + Clone
{
/// Create new [`PathDatabase`] from a [`Path`](std::path::Path).
pub fn from_path<S>(path: S, data: Data)
-> error::Result<FileDatabase<Data, DeSer>>
where S: AsRef<std::path::Path>
{
let backend = FileBackend::open(path).context(error::RustbreakErrorKind::Backend)?;
Ok(Database {
data: RwLock::new(data),
backend: Mutex::new(backend),
deser: DeSer::default(),
})
}
}
/// A database backed by a `Vec<u8>`
pub type MemoryDatabase<D, DS> = Database<D, MemoryBackend, DS>;