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<PathInfo>
}
impl PathInfo {
pub fn from_path(config: &Config, path: &str) -> Result<Self> {
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<Self> {
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<PathInfo> = 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<Self> {
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<Self> {
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<LocationRoot>)> = 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(())
}
}