wrestic 1.3.0

Restic wrapper built in Rust
use crate::utils::macros::error;
use anyhow::{Context, Result};
use color_print::cformat;
use config::Config;
use dialoguer::{theme::ColorfulTheme, Select};
use lazy_static::lazy_static;
use std::{collections::HashMap, fs, path::PathBuf, sync::Mutex};

#[derive(Debug)]
pub struct Settings {
    pub user: String,
    pub backend: String,
    pub name: String,
    pub repository: String,
    pub restic_password: String,
    pub backup_folder: String,
    pub restore_folder: String,
    pub keep_last: String,
    pub env: Option<HashMap<String, String>>,
}

lazy_static! {
    static ref USER_CHOICE: Mutex<Option<PathBuf>> = Mutex::new(None);
}

fn find_config_file() -> Option<PathBuf> {
    if let Ok(user_choice) = USER_CHOICE.lock() {
        if let Some(path) = user_choice.to_owned() {
            return Some(path);
        }
    }
    let home_dir = PathBuf::from("/home/");
    let mut config_paths = Vec::new();
    for entry in fs::read_dir(home_dir).ok()? {
        let entry = entry.ok()?;
        let mut config_path = entry.path();
        config_path.push(".config/wrestic/wrestic.toml");
        if config_path.exists() {
            config_paths.push(config_path);
        }
    }
    if config_paths.is_empty() {
        let root_dir = PathBuf::from("/root/");
        let mut config_path = root_dir;
        config_path.push(".config/wrestic/wrestic.toml");
        if config_path.exists() {
            config_paths.push(config_path);
        }
    }
    if config_paths.is_empty() {
        None
    } else if config_paths.len() == 1 {
        Some(config_paths[0].to_path_buf())
    } else {
        let items: Vec<&str> = config_paths
            .iter()
            .map(|p| p.to_str().unwrap_or_default())
            .collect();
        let selection = Select::with_theme(&ColorfulTheme::default())
            .with_prompt(cformat!("<y>Which config file do you want to use?"))
            .default(0)
            .items(&items[..])
            .interact()
            .unwrap();
        let result = Some(config_paths[selection].to_path_buf());
        if let Ok(mut user_choice) = USER_CHOICE.lock() {
            *user_choice = result.to_owned();
        }
        result
    }
}

pub fn get_config() -> Result<Vec<Settings>> {
    let config = Config::builder()
        .add_source(config::File::with_name(
            find_config_file()
                .context(error!("Failed to find config file"))?
                .to_str()
                .context(error!("Failed to convert config path to string"))?,
        ))
        .build()?;

    let user = find_config_file()
        .unwrap()
        .iter()
        .nth(2)
        .and_then(|f| f.to_str())
        .unwrap_or_default()
        .to_string();

    let settings_table = config.get_table("settings")?;

    let mut settings: Vec<Settings> = Vec::new();

    for (key, value) in &settings_table {
        let deserialized_value = value.to_owned().try_deserialize::<serde_json::Value>()?;

        let settings_struct = Settings {
            user: user.to_owned().replace('\"', ""),
            name: key.to_string().replace('\"', ""),

            backend: deserialized_value
                .get("BACKEND")
                .context(error!(format!(
                    "Failed to get the value of BACKEND for {key}"
                )))?
                .to_string()
                .replace('\"', ""),
            repository: deserialized_value
                .get("REPOSITORY")
                .context(error!(format!(
                    "Failed to get the value of REPOSITORY for {key}"
                )))?
                .to_string()
                .replace('\"', ""),
            restic_password: deserialized_value
                .get("RESTIC_PASSWORD")
                .context(error!(format!(
                    "Failed to get the value of RESTIC_PASSWORD for {key}"
                )))?
                .to_string()
                .replace('\"', ""),
            backup_folder: deserialized_value
                .get("BACKUP_FOLDER")
                .context(error!(format!(
                    "Failed to get the value of BACKUP_FOLER for {key}"
                )))?
                .to_string()
                .replace('\"', ""),
            restore_folder: deserialized_value
                .get("RESTORE_FOLDER")
                .context(error!(format!(
                    "Failed to get the value of RESTORE_FOLDER for {key}"
                )))?
                .to_string()
                .replace('\"', ""),
            keep_last: deserialized_value
                .get("KEEP_LAST")
                .context(error!(format!(
                    "Failed to get the value of KEEP_LAST for {key}"
                )))?
                .to_string()
                .replace('\"', ""),
            env: deserialized_value.get("env").iter().next().map(|x| {
                x.as_object()
                    .unwrap()
                    .iter()
                    .map(|(k, v)| (k.to_string(), v.to_string().replace('\"', "")))
                    .collect()
            }),
        };

        settings.push(settings_struct);
    }
    settings.sort_by(|a, b| a.name.cmp(&b.name));

    Ok(settings)
}