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, Box> { 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> { 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> { 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, Box> { 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() ); }