use crate::app::AppState; use std::path::{Path, PathBuf}; #[derive(Clone, Debug)] pub struct StaticFilesManager { pub dir: PathBuf, pub index: Vec<String>, } const STATIC_ASSETS_DIRECTORIES: [&'static str; 6] = [ "images", "sounds", "videos", "docs", "source_code", "default", ]; impl StaticFilesManager { pub fn new(app_state: &AppState) -> Result<Self, String> { match Self::create_dir_if_missing(&app_state.config.storage_dir) { Ok(dir) => Ok(StaticFilesManager { index: Vec::new(), dir, }), Err(msg) => Err(msg), } } #[cfg(test)] pub fn testing_new(test_dir: &PathBuf) -> Result<Self, String> { match Self::create_dir_if_missing(test_dir) { Ok(dir) => Ok(StaticFilesManager { index: Vec::new(), dir, }), Err(msg) => Err(msg), } } fn create_dir_if_missing(app_dir: &PathBuf) -> Result<PathBuf, String> { let static_dir = app_dir.join("static"); if !static_dir.exists() { match std::fs::create_dir_all(&static_dir) { Ok(_) => { if let Err(err) = Self::create_assets_directories_structure(&static_dir) { return Err(format!("{}", err)); }; if let Err(err) = Self::copy_default_files(&static_dir) { return Err(format!("{}", err)); } } Err(err) => return Err(format!("{}", err)), } } Ok(static_dir) } fn create_assets_directories_structure(root: &PathBuf) -> Result<(), std::io::Error> { for d in STATIC_ASSETS_DIRECTORIES { std::fs::create_dir(root.join(d))?; } Ok(()) } fn copy_default_files(static_dir: &PathBuf) -> Result<(), String> { let local_default_static = std::env::current_dir().unwrap().join("default_static"); let default_static = static_dir.join("default"); let mut cpy_options = fs_extra::dir::CopyOptions::new(); cpy_options.content_only = true; match fs_extra::dir::copy(local_default_static, default_static, &cpy_options) { Err(err) => Err(format!("{}", err)), Ok(_) => Ok(()), } } fn rec_read_dir(&mut self, root: &Path, strip_from: &Path) { for entry in root.read_dir().unwrap() { if let Ok(entry) = entry { if entry.path().is_dir() { self.rec_read_dir(&entry.path(), strip_from); } else { self.push_path(&entry.path().strip_prefix(strip_from).unwrap()); } } } } fn validate_path(&self, path: &Path) -> bool { self.dir.join(self.clean_relative_path(path)).exists() } fn clean_relative_path(&self, path: &Path) -> PathBuf { match path.starts_with("/") { true => path.strip_prefix("/").unwrap().to_path_buf(), false => path.to_path_buf(), } } fn push_path(&mut self, path: &Path) -> Vec<PathBuf> { if self.validate_path(path) { self.index .push(format!("/{}", path.to_str().unwrap().to_owned())); vec![path.to_path_buf()] } else { println!( "[WARNING] Error building static file index. The file {:?} doesn't exist and will be removed from the index.", self.dir.join(self.clean_relative_path(path)), ); Vec::new() } } pub fn add_pathes(&mut self, pathes: &Vec<String>) -> Vec<PathBuf> { let mut added: Vec<PathBuf> = Vec::new(); for pth in pathes.iter() { let p = self.push_path(Path::new(pth)); added.extend(p.iter().map(|pb| pb.clone())); } added } pub fn build(&mut self) -> Self { self.index = Vec::new(); self.rec_read_dir(&self.dir.clone(), &self.dir.clone()); self.clone() } } #[cfg(test)] mod test_static_files_manager { use super::*; fn create_test_dir() -> PathBuf { let pth = PathBuf::from("./test"); let _ = std::fs::create_dir(&pth); pth } fn remove_test_dir(pth: &PathBuf) { let _ = std::fs::remove_dir_all(pth); } #[test] fn test_directory_structure() { let test_dir = create_test_dir(); let _manager = StaticFilesManager::testing_new(&test_dir).unwrap(); for d in STATIC_ASSETS_DIRECTORIES { let p = test_dir.join("static").join(d); let exists = p.exists(); assert!( exists, "{} doesn't exist\n{:?}", p.display(), remove_test_dir(&test_dir) ); } remove_test_dir(&test_dir); } #[test] fn test_indexation() { let test_dir = create_test_dir(); let mut manager = StaticFilesManager::testing_new(&test_dir).unwrap(); let file_pth = test_dir.join("static").join("docs").join("testing.txt"); std::fs::File::create(&file_pth).unwrap(); manager = manager.build(); assert!( manager.index.contains(&"/docs/testing.txt".to_string()), "Index doesn't contain path /docs/testing.txt\n{:?}", remove_test_dir(&test_dir) ); remove_test_dir(&test_dir); } #[test] fn test_push_path() { let test_dir = create_test_dir(); let mut manager = StaticFilesManager::testing_new(&test_dir).unwrap().build(); let file_pth = test_dir.join("static").join("docs").join("testing.txt"); std::fs::File::create(&file_pth).unwrap(); let indexed_path = Path::new("docs/testing.txt"); let added = manager.push_path(&indexed_path); assert_eq!( added, vec![PathBuf::from("docs/testing.txt")], "Path was not added\n{:?}", remove_test_dir(&test_dir) ); assert!( manager.index.contains(&"/docs/testing.txt".to_string()), "Index doesn't contain path /docs/testing.txt\n{:?}", remove_test_dir(&test_dir) ); remove_test_dir(&test_dir); } #[test] fn test_push_unexisting_path() { let test_dir = create_test_dir(); let mut manager = StaticFilesManager::testing_new(&test_dir).unwrap().build(); let indexed_path = Path::new("images/unexisting.png"); let added = manager.push_path(&indexed_path); assert_eq!( added.len(), 0, "No path should have been added\n{:?}", remove_test_dir(&test_dir) ); assert!( !manager .index .contains(&"/images/unexisting.png".to_string()), "Index shouldn't container unexisting path\n{:?}", remove_test_dir(&test_dir) ); remove_test_dir(&test_dir); } }