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 database;
|
||||
mod user;
|
mod user;
|
||||
|
mod database_dump;
|
||||
|
|
||||
pub use self::database::MariaDBDatabase;
|
pub use self::database::MariaDBDatabase;
|
||||
|
pub use self::database_dump::DatabaseDump;
|
||||
pub use self::user::MariaDBUser;
|
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