summaryrefslogblamecommitdiff
path: root/src/pathinfo.rs
blob: b0c3be42fa8beaaba82110ef744e7c421e92b043 (plain) (tree)





















































































































































































































































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