Adrian Heine
8 years ago
9 changed files with 346 additions and 3 deletions
-
1Cargo.toml
-
1src/lib.rs
-
43src/storage.rs
-
136src/symbols/if_already_present.rs
-
6src/symbols/mariadb/database.rs
-
55src/symbols/mariadb/database_dump.rs
-
2src/symbols/mariadb/mod.rs
-
1src/symbols/mod.rs
-
104tests/storage.rs
@ -0,0 +1,43 @@ |
|||
use std::error::Error;
|
|||
use std::fs::read_dir;
|
|||
use std::str::FromStr;
|
|||
use std::time::{SystemTime, UNIX_EPOCH};
|
|||
|
|||
pub trait Storage {
|
|||
fn write_filename(&self) -> String;
|
|||
fn read_filename(&self) -> Result<String, Box<Error>>;
|
|||
fn recent_date(&self) -> Result<u64, Box<Error>>;
|
|||
}
|
|||
|
|||
pub struct SimpleStorage(String, String);
|
|||
|
|||
impl SimpleStorage {
|
|||
pub fn new(base: String, filename: String) -> Self {
|
|||
SimpleStorage(base, filename)
|
|||
}
|
|||
|
|||
fn get_path(&self, date: Option<u64>) -> String {
|
|||
match date {
|
|||
Some(d) => format!("{}/_{}/{}", self.0, self.1, d),
|
|||
None => format!("{}/_{}", self.0, self.1)
|
|||
}
|
|||
}
|
|||
}
|
|||
|
|||
impl Storage for SimpleStorage {
|
|||
fn write_filename(&self) -> String {
|
|||
self.get_path(Some(SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs()))
|
|||
}
|
|||
|
|||
fn read_filename(&self) -> Result<String, Box<Error>> {
|
|||
Ok(self.get_path(Some(try!(self.recent_date()))))
|
|||
}
|
|||
|
|||
fn recent_date(&self) -> Result<u64, Box<Error>> {
|
|||
let dir = self.get_path(None);
|
|||
try!(read_dir(dir))
|
|||
.map(|entry| entry.ok().and_then(|e| e.file_name().into_string().ok()).and_then(|filename| u64::from_str(&filename).ok()))
|
|||
.fold(None, |maybe_newest, maybe_time| maybe_newest.into_iter().chain(maybe_time).max())
|
|||
.ok_or("Not found".to_string().into())
|
|||
}
|
|||
}
|
@ -0,0 +1,136 @@ |
|||
use std::error::Error;
|
|||
use std::fmt;
|
|||
|
|||
use resources::Resource;
|
|||
use symbols::Symbol;
|
|||
|
|||
pub struct IfAlreadyPresent<A, B> where A: Symbol, B: Symbol {
|
|||
a: A,
|
|||
b: B
|
|||
}
|
|||
|
|||
impl<A, B> IfAlreadyPresent<A, B> where A: Symbol, B: Symbol {
|
|||
pub fn new(a: A, b: B) -> Self {
|
|||
IfAlreadyPresent { a: a, b: b }
|
|||
}
|
|||
}
|
|||
|
|||
impl<A, B> Symbol for IfAlreadyPresent<A, B> where A: Symbol, B: Symbol {
|
|||
fn target_reached(&self) -> Result<bool, Box<Error>> {
|
|||
self.a.target_reached().and_then(|reached| if reached { self.b.target_reached() } else { Ok(reached) })
|
|||
}
|
|||
|
|||
fn execute(&self) -> Result<(), Box<Error>> {
|
|||
// Execute a & b if a is not reached
|
|||
// Execute b if a was reached and b isn't
|
|||
if !try!(self.a.target_reached()) {
|
|||
try!(self.a.execute());
|
|||
self.b.execute()
|
|||
} else if !try!(self.b.target_reached()) {
|
|||
self.b.execute()
|
|||
} else {
|
|||
Ok(())
|
|||
}
|
|||
}
|
|||
|
|||
fn get_prerequisites(&self) -> Vec<Resource> {
|
|||
let mut r = vec![];
|
|||
r.extend(self.a.get_prerequisites().into_iter());
|
|||
r.extend(self.b.get_prerequisites().into_iter());
|
|||
r
|
|||
}
|
|||
|
|||
fn provides(&self) -> Option<Vec<Resource>> {
|
|||
let mut r = vec![];
|
|||
if let Some(provides) = self.a.provides() {
|
|||
r.extend(provides.into_iter());
|
|||
}
|
|||
if let Some(provides) = self.b.provides() {
|
|||
r.extend(provides.into_iter());
|
|||
}
|
|||
if r.len() > 0 { Some(r) } else { None }
|
|||
}
|
|||
}
|
|||
|
|||
impl<A, B> fmt::Display for IfAlreadyPresent<A, B> where A: Symbol, B: Symbol {
|
|||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>{
|
|||
write!(f, "IfAlreadyPresent {} and then {}", self.a, self.b)
|
|||
}
|
|||
}
|
|||
|
|||
#[cfg(test)]
|
|||
mod test {
|
|||
use std::error::Error;
|
|||
use std::fmt;
|
|||
|
|||
use symbols::Symbol;
|
|||
use symbols::if_already_present::IfAlreadyPresent;
|
|||
|
|||
struct ErrSymbol(String);
|
|||
impl Symbol for ErrSymbol {
|
|||
fn target_reached(&self) -> Result<bool, Box<Error>> { Err(self.0.clone().into()) }
|
|||
fn execute(&self) -> Result<(), Box<Error>> { Err(self.0.clone().into()) }
|
|||
}
|
|||
impl fmt::Display for ErrSymbol { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>{ write!(f, "") } }
|
|||
|
|||
struct OkSymbol(bool);
|
|||
impl Symbol for OkSymbol {
|
|||
fn target_reached(&self) -> Result<bool, Box<Error>> { Ok(self.0) }
|
|||
fn execute(&self) -> Result<(), Box<Error>> { Ok(()) }
|
|||
}
|
|||
impl fmt::Display for OkSymbol { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>{ write!(f, "") } }
|
|||
|
|||
#[test]
|
|||
fn first_target_reached_fails() {
|
|||
let res = IfAlreadyPresent::new(ErrSymbol("first".into()), ErrSymbol("second".into())).target_reached();
|
|||
assert_eq!(res.unwrap_err().description(), "first");
|
|||
}
|
|||
|
|||
#[test]
|
|||
fn first_target_not_reached() {
|
|||
let res = IfAlreadyPresent::new(OkSymbol(false), ErrSymbol("second".into())).target_reached();
|
|||
assert_eq!(res.unwrap(), false);
|
|||
}
|
|||
|
|||
#[test]
|
|||
fn second_target_reached_fails() {
|
|||
let res = IfAlreadyPresent::new(OkSymbol(true), ErrSymbol("second".into())).target_reached();
|
|||
assert_eq!(res.unwrap_err().description(), "second");
|
|||
}
|
|||
|
|||
#[test]
|
|||
fn second_target_not_reached() {
|
|||
let res = IfAlreadyPresent::new(OkSymbol(true), OkSymbol(false)).target_reached();
|
|||
assert_eq!(res.unwrap(), false);
|
|||
}
|
|||
|
|||
#[test]
|
|||
fn everything_reached() {
|
|||
let res = IfAlreadyPresent::new(OkSymbol(true), OkSymbol(true)).target_reached();
|
|||
assert_eq!(res.unwrap(), true);
|
|||
}
|
|||
|
|||
#[test]
|
|||
fn first_execute_fails() {
|
|||
let res = IfAlreadyPresent::new(ErrSymbol("first".into()), ErrSymbol("second".into())).execute();
|
|||
assert_eq!(res.unwrap_err().description(), "first");
|
|||
}
|
|||
|
|||
#[test]
|
|||
fn second_execute_fails_but_isnt_run() {
|
|||
let res = IfAlreadyPresent::new(OkSymbol(false), ErrSymbol("second".into())).execute();
|
|||
assert_eq!(res.unwrap(), ());
|
|||
}
|
|||
|
|||
#[test]
|
|||
fn second_execute_fails() {
|
|||
let res = IfAlreadyPresent::new(OkSymbol(true), ErrSymbol("second".into())).execute();
|
|||
assert_eq!(res.unwrap_err().description(), "second");
|
|||
}
|
|||
|
|||
#[test]
|
|||
fn everything_executes() {
|
|||
let res = IfAlreadyPresent::new(OkSymbol(true), OkSymbol(true)).execute();
|
|||
assert_eq!(res.unwrap(), ());
|
|||
}
|
|||
}
|
@ -0,0 +1,55 @@ |
|||
use std::borrow::Cow;
|
|||
use std::error::Error;
|
|||
use std::fmt;
|
|||
use std::str::FromStr;
|
|||
|
|||
use command_runner::CommandRunner;
|
|||
use symbols::Symbol;
|
|||
use storage::Storage;
|
|||
|
|||
pub struct DatabaseDump<'a, S> where S: Storage {
|
|||
db_name: Cow<'a, str>,
|
|||
storage: S,
|
|||
command_runner: &'a CommandRunner
|
|||
}
|
|||
|
|||
impl<'a, S> DatabaseDump<'a, S> where S: Storage {
|
|||
pub fn new(db_name: Cow<'a, str>, storage: S, command_runner: &'a CommandRunner) -> Self {
|
|||
DatabaseDump {
|
|||
db_name: db_name,
|
|||
storage: storage,
|
|||
command_runner: command_runner
|
|||
}
|
|||
}
|
|||
|
|||
fn run_sql(&self, sql: &str) -> Result<String, Box<Error>> {
|
|||
let output = try!(self.command_runner.run_with_args("mariadb", &["--skip-column-names", "-B", "-e", sql]));
|
|||
if output.status.code() != Some(0) {
|
|||
return Err(try!(String::from_utf8(output.stderr)).into());
|
|||
}
|
|||
Ok(try!(String::from_utf8(output.stdout)))
|
|||
}
|
|||
}
|
|||
|
|||
impl<'a, S> fmt::Display for DatabaseDump<'a, S> where S: Storage {
|
|||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|||
write!(f, "MariaDB Database {}", self.db_name)
|
|||
}
|
|||
}
|
|||
|
|||
impl<'a, S> Symbol for DatabaseDump<'a, S> where S: Storage {
|
|||
fn target_reached(&self) -> Result<bool, Box<Error>> {
|
|||
let dump_date = try!(self.storage.recent_date());
|
|||
let modified_date = try!(self.run_sql(&format!("select UNIX_TIMESTAMP(MAX(UPDATE_TIME)) from information_schema.tables WHERE table_schema = '{}'", self.db_name)));
|
|||
Ok(try!(u64::from_str(modified_date.trim_right())) <= dump_date)
|
|||
}
|
|||
|
|||
fn execute(&self) -> Result<(), Box<Error>> {
|
|||
try!(self.command_runner.run_with_args("sh", &["-c", &format!("mysqldump '{}' > {}", self.db_name, self.storage.write_filename())]));
|
|||
Ok(())
|
|||
}
|
|||
}
|
|||
|
|||
#[cfg(test)]
|
|||
mod test {
|
|||
}
|
@ -1,5 +1,7 @@ |
|||
mod database;
|
|||
mod user;
|
|||
mod database_dump;
|
|||
|
|||
pub use self::database::MariaDBDatabase;
|
|||
pub use self::database_dump::DatabaseDump;
|
|||
pub use self::user::MariaDBUser;
|
@ -0,0 +1,104 @@ |
|||
extern crate regex;
|
|||
extern crate schematics;
|
|||
extern crate tempdir;
|
|||
|
|||
use regex::Regex;
|
|||
use std::fs::{create_dir, File};
|
|||
use std::path::Path;
|
|||
use tempdir::TempDir;
|
|||
use schematics::storage::{SimpleStorage, Storage};
|
|||
|
|||
fn get_dir<'a, I: IntoIterator<Item=&'a &'a str>>(content: I) -> TempDir {
|
|||
let tmp_dir = TempDir::new("unittest").expect("create temp dir");
|
|||
let storage_path = tmp_dir.path().join("_filename");
|
|||
create_dir(storage_path.clone()).unwrap();
|
|||
for path in content {
|
|||
let file_path = storage_path.join(path);
|
|||
File::create(file_path).expect("create temp file");
|
|||
}
|
|||
tmp_dir
|
|||
}
|
|||
|
|||
fn get_storage(path: &Path) -> SimpleStorage {
|
|||
SimpleStorage::new(path.to_str().unwrap().into(), "filename".into())
|
|||
}
|
|||
|
|||
// Normal cases
|
|||
|
|||
#[test]
|
|||
fn single_file() {
|
|||
let dir = get_dir(&["12345"]);
|
|||
let storage = get_storage(dir.path());
|
|||
|
|||
assert!(Regex::new(&format!("^{}/_filename/\\d+$", dir.path().display())).unwrap().is_match(&storage.write_filename()));
|
|||
assert_eq!(dir.path().join("_filename").join("12345"), Path::new(&storage.read_filename().unwrap()));
|
|||
assert_eq!(storage.recent_date().unwrap(), 12345);
|
|||
}
|
|||
|
|||
#[test]
|
|||
fn two_files() {
|
|||
let dir = get_dir(&["12345", "23456"]);
|
|||
let storage = get_storage(dir.path());
|
|||
|
|||
assert!(Regex::new(&format!("^{}/_filename/\\d+$", dir.path().display())).unwrap().is_match(&storage.write_filename()));
|
|||
assert_eq!(dir.path().join("_filename").join("23456"), Path::new(&storage.read_filename().unwrap()));
|
|||
assert_eq!(storage.recent_date().unwrap(), 23456);
|
|||
}
|
|||
|
|||
#[test]
|
|||
fn another_two_files() {
|
|||
let dir = get_dir(&["23456", "12345"]);
|
|||
let storage = get_storage(dir.path());
|
|||
|
|||
assert!(Regex::new(&format!("^{}/_filename/\\d+$", dir.path().display())).unwrap().is_match(&storage.write_filename()));
|
|||
assert_eq!(dir.path().join("_filename").join("23456"), Path::new(&storage.read_filename().unwrap()));
|
|||
assert_eq!(storage.recent_date().unwrap(), 23456);
|
|||
}
|
|||
|
|||
#[test]
|
|||
fn three_files() {
|
|||
let dir = get_dir(&["23456", "9", "12345"]);
|
|||
let storage = get_storage(dir.path());
|
|||
|
|||
assert!(Regex::new(&format!("^{}/_filename/\\d+$", dir.path().display())).unwrap().is_match(&storage.write_filename()));
|
|||
assert_eq!(dir.path().join("_filename").join("23456"), Path::new(&storage.read_filename().unwrap()));
|
|||
assert_eq!(storage.recent_date().unwrap(), 23456);
|
|||
}
|
|||
|
|||
// Bad cases
|
|||
|
|||
#[test]
|
|||
fn empty_storage() {
|
|||
let dir = TempDir::new("unittest").expect("create temp dir");
|
|||
let storage = get_storage(dir.path());
|
|||
|
|||
assert!(Regex::new(&format!("^{}/_filename/\\d+$", dir.path().display())).unwrap().is_match(&storage.write_filename()));
|
|||
assert!(storage.read_filename().is_err());
|
|||
assert_eq!(storage.read_filename().unwrap_err().description(), "entity not found");
|
|||
assert!(storage.recent_date().is_err());
|
|||
assert_eq!(storage.recent_date().unwrap_err().description(), "entity not found");
|
|||
}
|
|||
|
|||
#[test]
|
|||
fn empty_storage_for_filename() {
|
|||
let dir = get_dir(&[]);
|
|||
let storage = get_storage(dir.path());
|
|||
|
|||
assert!(Regex::new(&format!("^{}/_filename/\\d+$", dir.path().display())).unwrap().is_match(&storage.write_filename()));
|
|||
assert!(storage.read_filename().is_err());
|
|||
assert_eq!(storage.read_filename().unwrap_err().description(), "Not found");
|
|||
assert!(storage.recent_date().is_err());
|
|||
assert_eq!(storage.recent_date().unwrap_err().description(), "Not found");
|
|||
}
|
|||
|
|||
#[test]
|
|||
fn bad_file() {
|
|||
let dir = get_dir(&["abba"]);
|
|||
let storage = get_storage(dir.path());
|
|||
|
|||
assert!(Regex::new(&format!("^{}/_filename/\\d+$", dir.path().display())).unwrap().is_match(&storage.write_filename()));
|
|||
assert!(storage.read_filename().is_err());
|
|||
assert_eq!(storage.read_filename().unwrap_err().description(), "Not found");
|
|||
assert!(storage.recent_date().is_err());
|
|||
assert_eq!(storage.recent_date().unwrap_err().description(), "Not found");
|
|||
}
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue