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(()) } }