use std::{ fmt::Display, path::PathBuf, }; use serde::{Deserialize, Serialize}; use crate::{ backup::BackupId, config::Config, error::{Error, Result}, }; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PathInfo { pub modified: bool, pub is_file: bool, rel_location: String, location_root: LocationRoot, last_modified: BackupId, children: Vec } impl PathInfo { pub fn from_path(config: &Config, path: &str) -> Result { let locations = Self::parse_location(path, config)?; Ok(Self::handle_dir(config, &locations.0, &locations.1)?) } fn handle_dir( config: &Config, rel_location: &str, location_root: &LocationRoot, ) -> Result { println!("Handling {rel_location}"); let path = Self::get_abs_path(&location_root.to_string(), rel_location); Ok(if path.is_dir() { let mut modified = false; let mut children: Vec = Vec::new(); let paths = std::fs::read_dir(path).unwrap(); for path in paths { let pathstr = path.unwrap().path().to_string_lossy().to_string(); let root = format!("{}/", location_root.to_string()); let Some(rl) = pathstr.split_once(&root) else { panic!("HUH"); }; let handle = Self::handle_dir(config, rl.1, location_root)?; if handle.modified { modified = true; }; children.push(handle); } Self { modified, is_file: false, rel_location: rel_location.to_string(), location_root: location_root.clone(), last_modified: "".to_string(), children } } else { Self::from_file(rel_location, location_root.clone())? }) } fn from_file(rel_location: &str, location_root: LocationRoot) -> Result { println!("From file {rel_location}"); let modified = false; Ok(Self { rel_location: rel_location.to_string(), location_root, modified, last_modified: "".to_string(), is_file: true, children: Vec::new() }) } pub fn get_absolute_path(&self) -> PathBuf { Self::get_abs_path(&self.location_root.to_string(), &self.rel_location) } fn get_abs_path(location_root: &str, rel_location: &str) -> PathBuf { let path = format!("{}/{}", location_root, rel_location); PathBuf::from(path) } fn parse_location(value: &str, config: &Config) -> Result<(String, LocationRoot)> { let Some(split) = value.split_once('/') else { return Err(Error::InvalidDirectory(value.to_string())); }; if split.0.starts_with('~') { if config.user.len() != 1 { return Err(Error::MultiUser); } return Ok(( split.1.to_string(), LocationRoot::User(config.user[0].clone()), )); }; Ok(( split.1.to_string(), LocationRoot::from_op_str(split.0, config)?, )) } } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum LocationRoot { User(String), Custom(String), SystemSettings, Root, } impl Display for LocationRoot { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { LocationRoot::User(user) => write!(f, "/home/{user}"), LocationRoot::Custom(loc) => write!(f, "{loc}"), LocationRoot::SystemSettings => write!(f, "/etc"), LocationRoot::Root => write!(f, "/"), } } } impl LocationRoot { fn from_op_str(value: &str, config: &Config) -> Result { let split_str = value.split_once(':'); let Some(split_op) = split_str else { return Err(Error::NoIndex); }; match split_op.0 { "u" => Ok(Self::User(split_op.1.to_string())), "s" => Ok(Self::SystemSettings), "r" => Ok(Self::Root), "c" => Ok(Self::Custom( config .custom_directories .get(split_op.1) .ok_or_else(|| Error::CustomDirectory(split_op.1.to_string()))? .to_string(), )), _ => Err(Error::InvalidIndex(split_op.0.to_string())), } } } // #[cfg(test)] // mod tests { // use crate::{ // config::Config, // error::{Error, Result}, // pathinfo::PathInfo, // }; // // use super::LocationRoot; // // #[test] // fn from_op_str() -> Result<()> { // let mut config = Config::default(); // config // .custom_directories // .insert("test".to_string(), "/usr/local/test".to_string()); // // let mut values: Vec<(&str, Result)> = Vec::new(); // values.push(("u:test", Ok(LocationRoot::User("test".to_string())))); // values.push(("s:", Ok(LocationRoot::SystemSettings))); // values.push(("r:", Ok(LocationRoot::Root))); // values.push(( // "c:test", // Ok(LocationRoot::Custom("/usr/local/test".to_string())), // )); // values.push(("c:rest", Err(Error::CustomDirectory("rest".to_string())))); // values.push(("t:test/", Err(Error::InvalidIndex("t".to_string())))); // values.push(( // "test:test/usr", // Err(Error::InvalidIndex("test".to_string())), // )); // values.push(("/usr/local/test", Err(Error::NoIndex))); // values.push(("c/usr/local/test", Err(Error::NoIndex))); // // for value in values { // print!("Testing {value:?}"); // assert_eq!(LocationRoot::from_op_str(value.0, &config), value.1); // println!("\rTesting {value:?} ✓"); // } // // Ok(()) // } // // #[test] // fn parse_location() -> Result<()> { // let mut config = Config::default(); // config.user.push("test".to_string()); // config // .custom_directories // .insert("test".to_string(), "/usr/local/test".to_string()); // // let mut values: Vec<(&str, Result<(String, LocationRoot)>)> = Vec::new(); // values.push(( // "~/.config/nvim", // Ok(( // ".config/nvim".to_string(), // LocationRoot::User("test".to_string()), // )), // )); // values.push(( // "u:test/.config/nvim", // Ok(( // ".config/nvim".to_string(), // LocationRoot::User("test".to_string()), // )), // )); // values.push(( // "r:/.config/nvim", // Ok((".config/nvim".to_string(), LocationRoot::Root)), // )); // values.push(( // "r:/.config/nvim", // Ok((".config/nvim".to_string(), LocationRoot::Root)), // )); // values.push(( // "s:/.config/nvim", // Ok((".config/nvim".to_string(), LocationRoot::SystemSettings)), // )); // values.push(( // "c:test/.config/nvim", // Ok(( // ".config/nvim".to_string(), // LocationRoot::Custom("/usr/local/test".to_string()), // )), // )); // // for value in values { // print!("Testing {value:?}"); // assert_eq!(PathInfo::parse_location(&value.0, &config), value.1); // println!("\rTesting {value:?} ✓"); // } // Ok(()) // } // }