2020-11-08 12:16:03 +00:00
|
|
|
#![allow(unused)]
|
2020-11-14 03:02:02 +00:00
|
|
|
|
2020-10-30 04:02:43 +00:00
|
|
|
use std::fs;
|
2020-11-14 21:12:53 +00:00
|
|
|
use std::io::{prelude::*, Error, ErrorKind};
|
2020-11-11 18:10:37 +00:00
|
|
|
use std::path;
|
2020-10-30 04:02:43 +00:00
|
|
|
|
2020-11-14 03:02:02 +00:00
|
|
|
use regex;
|
|
|
|
|
2020-10-30 04:02:43 +00:00
|
|
|
#[derive(Debug, PartialEq)]
|
|
|
|
pub struct Store {
|
|
|
|
path: String,
|
|
|
|
buffer: Vec<u8>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Store {
|
|
|
|
pub fn new() -> Self {
|
|
|
|
Store {
|
|
|
|
path: String::default(),
|
|
|
|
buffer: vec![],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn from(path: String) -> Store {
|
|
|
|
Store {
|
2020-11-11 18:10:37 +00:00
|
|
|
path: Store::normalize_path(path),
|
2020-10-30 04:02:43 +00:00
|
|
|
buffer: vec![],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn open(&mut self) -> Result<(), Error> {
|
|
|
|
if self.path.is_empty() {
|
|
|
|
return Err(Error::new(ErrorKind::InvalidInput, "empty path for store"));
|
|
|
|
}
|
2020-11-11 18:10:37 +00:00
|
|
|
let p = self.path.clone();
|
|
|
|
Store::ensure_dir(p.clone());
|
2020-11-14 21:12:53 +00:00
|
|
|
let f = fs::OpenOptions::new()
|
2020-10-30 04:02:43 +00:00
|
|
|
.append(true)
|
|
|
|
.create(true)
|
2020-11-11 18:10:37 +00:00
|
|
|
.open(p.clone())?;
|
|
|
|
match fs::read(p) {
|
2020-11-08 12:16:03 +00:00
|
|
|
Ok(buff) => {
|
|
|
|
self.buffer = buff;
|
|
|
|
Ok(())
|
|
|
|
}
|
2020-10-30 04:02:43 +00:00
|
|
|
Err(err) => Err(err),
|
|
|
|
}
|
|
|
|
}
|
2020-11-08 15:23:01 +00:00
|
|
|
|
|
|
|
pub fn save(&mut self) -> Result<(), Error> {
|
2020-11-11 18:10:37 +00:00
|
|
|
Store::ensure_dir(self.path.clone());
|
|
|
|
fs::write(
|
|
|
|
Store::normalize_path(self.path.clone()),
|
|
|
|
self.buffer.clone(),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn normalize_path(p: String) -> String {
|
|
|
|
let re = regex::Regex::new(r"[\\,/]+").unwrap();
|
|
|
|
let sep = path::MAIN_SEPARATOR.to_string();
|
|
|
|
re.replace_all(p.as_str(), sep.as_str()).to_string()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn ensure_dir(p: String) {
|
|
|
|
let np = Store::normalize_path(p);
|
|
|
|
let pa = path::PathBuf::from(np);
|
|
|
|
if !pa.exists() {
|
|
|
|
fs::create_dir_all(pa.parent().unwrap());
|
|
|
|
};
|
2020-11-08 15:23:01 +00:00
|
|
|
}
|
2020-10-30 04:02:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2020-11-14 03:02:02 +00:00
|
|
|
use super::Store;
|
2020-11-14 21:12:53 +00:00
|
|
|
use regex;
|
|
|
|
use std::{fs, io, iter::FromIterator, path, path::MAIN_SEPARATOR, time::SystemTime};
|
2020-11-14 03:02:02 +00:00
|
|
|
|
2020-11-11 18:10:37 +00:00
|
|
|
const TMP_DIR: [&'static str; 5] = ["/tmp", "pigeon_core", "test_data", "storage", "store"];
|
2020-10-30 04:02:43 +00:00
|
|
|
|
|
|
|
fn format_file_name(name: &str) -> String {
|
2020-11-11 18:10:37 +00:00
|
|
|
let sep = String::from(MAIN_SEPARATOR.clone());
|
|
|
|
let mut pc: Vec<&str> = vec![];
|
|
|
|
for dir in TMP_DIR.to_vec() {
|
|
|
|
pc.push(dir);
|
|
|
|
}
|
|
|
|
pc.push(name);
|
|
|
|
pc.join(&sep)
|
2020-10-30 04:02:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn setup_tmp_dir() -> Result<(), io::Error> {
|
2020-11-11 18:10:37 +00:00
|
|
|
fs::create_dir_all(TMP_DIR.join(MAIN_SEPARATOR.to_string().as_str()))
|
2020-10-30 04:02:43 +00:00
|
|
|
}
|
|
|
|
|
2020-11-08 12:16:03 +00:00
|
|
|
fn setup_tmp_file(name: &str, buf: Option<Vec<u8>>) -> Result<String, io::Error> {
|
2020-10-30 04:02:43 +00:00
|
|
|
setup_tmp_dir()?;
|
|
|
|
let t = SystemTime::now()
|
|
|
|
.duration_since(SystemTime::UNIX_EPOCH)
|
|
|
|
.unwrap();
|
|
|
|
let now = format!("{}-{:?}", name, t);
|
|
|
|
let path = format_file_name(&now);
|
2020-11-08 12:16:03 +00:00
|
|
|
let result = match buf {
|
|
|
|
Some(c) => fs::write(path.clone(), c),
|
|
|
|
None => fs::write(path.clone(), ""),
|
|
|
|
};
|
|
|
|
match result {
|
|
|
|
Err(err) => Err(err),
|
|
|
|
_ => Ok(path),
|
2020-10-30 04:02:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn teardown_tmp_file(name: &str) {
|
|
|
|
let _ = fs::remove_file(format_file_name(name));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2020-11-08 12:16:03 +00:00
|
|
|
fn returns_new_store() {
|
2020-10-30 04:02:43 +00:00
|
|
|
let s = Store::new();
|
|
|
|
let e = Store {
|
|
|
|
path: String::default(),
|
|
|
|
buffer: vec![],
|
|
|
|
};
|
|
|
|
assert_eq!(e, s);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2020-11-08 12:16:03 +00:00
|
|
|
fn returns_store_with_path() {
|
2020-10-30 04:02:43 +00:00
|
|
|
let path = String::from("foo");
|
2020-11-08 12:16:03 +00:00
|
|
|
let s = Store::from(path.clone());
|
2020-10-30 04:02:43 +00:00
|
|
|
let e = Store {
|
2020-11-14 02:28:23 +00:00
|
|
|
path,
|
2020-10-30 04:02:43 +00:00
|
|
|
buffer: vec![],
|
|
|
|
};
|
|
|
|
assert_eq!(e, s);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2020-11-08 12:16:03 +00:00
|
|
|
fn store_open_errors_with_empty_path() {
|
2020-10-30 04:02:43 +00:00
|
|
|
let mut s = Store::new();
|
|
|
|
let resp = s.open();
|
|
|
|
assert_eq!(true, resp.is_err());
|
|
|
|
let e = resp.unwrap_err();
|
|
|
|
assert_eq!(io::ErrorKind::InvalidInput, e.kind());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2020-11-08 12:16:03 +00:00
|
|
|
fn store_open_creates_new_file() {
|
2020-11-11 18:10:37 +00:00
|
|
|
fn setup_path() -> String {
|
|
|
|
let name = "store_open_creates_new_file";
|
|
|
|
let t = SystemTime::now()
|
|
|
|
.duration_since(SystemTime::UNIX_EPOCH)
|
|
|
|
.unwrap();
|
|
|
|
let now = format!("{}-{:?}", name, t);
|
|
|
|
format_file_name(&now)
|
|
|
|
}
|
|
|
|
let path = setup_path();
|
|
|
|
let mut s = Store::from(path.clone());
|
2020-10-30 04:02:43 +00:00
|
|
|
let resp = s.open();
|
|
|
|
assert_eq!(false, resp.is_err());
|
|
|
|
let eb: Vec<u8> = vec![];
|
|
|
|
assert_eq!(eb, s.buffer);
|
2020-11-11 18:10:37 +00:00
|
|
|
teardown_tmp_file(&*path);
|
2020-10-30 04:02:43 +00:00
|
|
|
}
|
2020-11-08 12:16:03 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn store_open_loads_existing_file() {
|
|
|
|
let buf: Vec<u8> = "i exist".as_bytes().to_vec();
|
|
|
|
let p = setup_tmp_file("store_load_existing", Some(buf.clone()));
|
|
|
|
assert_eq!(true, p.is_ok());
|
2020-11-11 18:10:37 +00:00
|
|
|
let path = p.unwrap();
|
|
|
|
let mut s = Store::from(path.clone());
|
2020-11-08 12:16:03 +00:00
|
|
|
let resp = s.open();
|
|
|
|
assert_eq!(true, resp.is_ok());
|
|
|
|
assert_eq!(buf, s.buffer);
|
2020-11-11 18:10:37 +00:00
|
|
|
teardown_tmp_file(&*path);
|
2020-11-08 12:16:03 +00:00
|
|
|
}
|
2020-11-11 18:10:37 +00:00
|
|
|
|
2020-11-08 15:23:01 +00:00
|
|
|
#[test]
|
|
|
|
fn store_save_create_new_file() {
|
|
|
|
fn setup_path() -> String {
|
|
|
|
let name = "store_save_create_new_file";
|
|
|
|
let t = SystemTime::now()
|
|
|
|
.duration_since(SystemTime::UNIX_EPOCH)
|
|
|
|
.unwrap();
|
|
|
|
let now = format!("{}-{:?}", name, t);
|
|
|
|
format_file_name(&now)
|
|
|
|
}
|
|
|
|
let path = setup_path();
|
2020-11-11 18:10:37 +00:00
|
|
|
let buf = "It's a strange time to be created".as_bytes().to_vec();
|
2020-11-08 15:23:01 +00:00
|
|
|
let mut s = Store::from(path.clone());
|
|
|
|
s.buffer = buf.clone();
|
|
|
|
assert_eq!(buf.clone(), s.buffer);
|
|
|
|
let mut resp = s.save();
|
|
|
|
assert_eq!(true, resp.is_ok());
|
|
|
|
|
2020-11-11 18:10:37 +00:00
|
|
|
let mut s = Store::from(path.clone());
|
2020-11-08 15:23:01 +00:00
|
|
|
let mut resp = s.open();
|
|
|
|
assert_eq!(true, resp.is_ok());
|
|
|
|
assert_eq!(buf, s.buffer);
|
2020-11-11 18:10:37 +00:00
|
|
|
teardown_tmp_file(&*path);
|
2020-11-08 15:23:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn store_save_updates_existing_file() {
|
|
|
|
let buf: Vec<u8> = "i exist".as_bytes().to_vec();
|
|
|
|
fn setup_buffer(buf: Vec<u8>) -> String {
|
2020-11-11 18:10:37 +00:00
|
|
|
let p = setup_tmp_file("store_save_updates_existing_file", Some(buf.clone()));
|
2020-11-08 15:23:01 +00:00
|
|
|
assert_eq!(true, p.is_ok());
|
|
|
|
p.unwrap()
|
|
|
|
}
|
|
|
|
fn setup_store(path: String) -> Store {
|
|
|
|
let mut s = Store::from(path);
|
|
|
|
let resp = s.open();
|
|
|
|
assert_eq!(true, resp.is_ok());
|
|
|
|
s
|
|
|
|
}
|
|
|
|
let path = setup_buffer(buf.clone());
|
|
|
|
let mut s = setup_store(path.clone());
|
|
|
|
assert_eq!(buf.clone(), s.buffer);
|
|
|
|
|
|
|
|
let new_buf = "I exist!".as_bytes().to_vec();
|
|
|
|
s.buffer = new_buf.clone();
|
|
|
|
let resp = s.save();
|
|
|
|
assert_eq!(true, resp.is_ok());
|
|
|
|
|
2020-11-11 18:10:37 +00:00
|
|
|
let mut s = setup_store(path.clone());
|
2020-11-08 15:23:01 +00:00
|
|
|
assert_ne!(buf, s.buffer);
|
|
|
|
assert_eq!(new_buf, s.buffer);
|
2020-11-11 18:10:37 +00:00
|
|
|
teardown_tmp_file(&*path);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn store_normalize_path() {
|
|
|
|
let re = regex::Regex::new(r"[\\,/]+").unwrap();
|
|
|
|
let sep = "\\";
|
|
|
|
let op = format_file_name("store_normalize_path");
|
|
|
|
let e = op.clone();
|
|
|
|
let mut a = op.clone().to_owned();
|
|
|
|
println!("{}, {}, {}", op.clone(), e.clone(), a.clone());
|
|
|
|
a = re.replace_all(a.as_str(), sep).to_string();
|
|
|
|
assert_ne!(e, a);
|
|
|
|
|
|
|
|
let r = Store::normalize_path(a.clone().to_owned());
|
|
|
|
assert_ne!(a, r);
|
|
|
|
assert_eq!(e, r);
|
2020-11-08 15:23:01 +00:00
|
|
|
}
|
2020-10-30 04:02:43 +00:00
|
|
|
}
|