Avarice_old/src/database/io.rs
2020-11-28 03:25:57 +07:00

225 lines
6.2 KiB
Rust

use super::*;
use log::{error, info, warn};
use std::collections::HashMap;
use std::error::Error;
use std::fs;
use std::path;
use std::path::Path;
extern crate custom_error;
use custom_error::custom_error;
const JSON_EXTENSION: &str = "json";
custom_error! { pub IOError
NotDirectory{path: String} = "Path to the database should point at a directory: {path}",
}
pub fn read(db_path: &path::Path) -> Result<Vec<Group>, Box<dyn Error>> {
if !db_path.is_dir() {
error!(
"Loading database from a non-directory {} was attempted.",
db_path.display()
);
return Err(Box::new(IOError::NotDirectory {
path: db_path.display().to_string(),
}));
}
info!("Loading database from {}.", db_path.display());
let mut groups = Vec::new();
for entry in fs::read_dir(db_path)? {
let path = match entry {
Ok(r) => r,
_ => continue,
}
.path();
if !check_valid_group_dir(path.as_path()) {
continue;
}
match read_group(&path)? {
Some(g) => groups.push(g),
_ => (),
}
}
info!("Correctly finished leading database.");
Ok(groups)
}
pub fn write(db_path: &path::Path, db: &Database) -> Result<(), Box<dyn Error>> {
if db_path.exists() && !db_path.is_dir() {
error!(
"Cannot write database into a non-directory {}",
db_path.display()
);
return Err(Box::new(IOError::NotDirectory {
path: db_path.display().to_string(),
}));
}
fs::create_dir(db_path)?;
for group in db.group_names().iter() {
let group_path = db_path.join(group);
if !group_path.exists() || !group_path.is_dir() {
fs::create_dir(group_path.clone())?;
}
for file in db.file_names_in(group).iter() {
let file_path = group_path.join(format!("{}.{}", file, JSON_EXTENSION));
match db.file(group, file) {
Some(file) => fs::write(file_path, file.to_string())?,
_ => (),
}
}
}
Ok(())
}
pub fn clear_dir(db_path: &path::Path) -> Result<(), Box<dyn Error>> {
info!(
"Clearing directory {} from database files.",
db_path.display()
);
if !db_path.exists() {
info!("Directory not found, nothing to do.");
return Ok(());
}
for entry in fs::read_dir(db_path)? {
let dir_path = match entry {
Ok(r) => r,
_ => continue,
}
.path();
if !check_valid_group_dir(dir_path.as_path()) {
continue;
}
for entry in fs::read_dir(dir_path.clone())? {
let file_path = match entry {
Ok(r) => r,
_ => continue,
}
.path();
if !check_valid_data_file(file_path.as_path()) {
continue;
}
fs::remove_file(file_path)?;
}
let _ = fs::remove_dir(dir_path);
}
let _ = fs::remove_dir(db_path);
info!("Correctly finished clearing database files.");
Ok(())
}
fn read_group(group_path: &path::Path) -> Result<Option<Group>, Box<dyn Error>> {
let mut files = HashMap::new();
for entry in fs::read_dir(group_path)? {
let path = match entry {
Ok(r) => r,
_ => continue,
}
.path();
if !check_valid_data_file(path.as_path()) {
continue;
}
let file_name = get_file_name(path.as_path());
let file_contents = fs::read_to_string(&path)?;
files.insert(file_name, File::new(file_contents)?);
}
if files.len() > 0 {
return Ok(Some(Group {
name: get_file_name(group_path),
files,
}));
}
Ok(None)
}
fn check_valid_group_dir(dir_path: &path::Path) -> bool {
if !dir_path.is_dir() {
warn!(
r#"Skipping {}, because only directories are expected in database's root."#,
dir_path.display()
);
return false;
}
if !is_name_valid(&get_file_name(dir_path)) {
warn!(
r#"Skipping directory {}, because it does not have a valid name."#,
dir_path.display()
);
return false;
}
true
}
fn check_valid_data_file(file_path: &path::Path) -> bool {
if file_path.is_dir() {
warn!(
r#"Skipping directory {}, because group directories are only\
supposed to contain files."#,
file_path.display()
);
return false;
}
let name = get_file_name(file_path);
if !is_name_valid(&name) {
warn!(
r#"Skipping file {}, because it does not have a valid name."#,
file_path.display()
);
return false;
}
let extension = get_file_extension(file_path);
if !JSON_EXTENSION.eq_ignore_ascii_case(&extension) {
warn!(
r#"Skipping file {}, because it does not have "json" extension."#,
file_path.display()
);
return false;
}
true
}
fn get_file_name(path: &path::Path) -> String {
path.file_stem()
.and_then(|x| x.to_str())
.unwrap_or_default()
.to_string()
}
fn get_file_extension(path: &path::Path) -> String {
path.extension()
.and_then(|x| x.to_str())
.unwrap_or_default()
.to_string()
}
#[test]
fn test_file_name_extension_extraction() {
assert_eq!(get_file_name(Path::new("/dir/file")), "file".to_owned());
assert_eq!(
get_file_name(Path::new("/dir/sub_dir/some.ext")),
"some".to_owned()
);
assert_eq!(
get_file_name(Path::new("/dir/sub_dir/.ext")),
".ext".to_owned()
);
assert_eq!(
get_file_name(Path::new("/dir/sub_dir/thing.")),
"thing".to_owned()
);
assert_eq!(get_file_extension(Path::new("/dir/file")), "".to_owned());
assert_eq!(
get_file_extension(Path::new("/dir/sub_dir/some.ext")),
"ext".to_owned()
);
assert_eq!(
get_file_extension(Path::new("/dir/sub_dir/.ext")),
"".to_owned()
);
assert_eq!(
get_file_extension(Path::new("/dir/sub_dir/thing.")),
"".to_owned()
);
}