mirror of
https://github.com/Drop-OSS/dropbreak.git
synced 2026-07-18 18:04:32 -04:00
Merge pull request #69 from niluxv/atomic_saves
Add `PathBackend` with atomic saves
This commit is contained in:
+1
-1
@@ -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 = []
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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>;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user