225 lines
6.2 KiB
Rust
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()
|
|
);
|
|
}
|