adof 0.12.0

ADOF - An Automatic Dot-files Organizer Friend
Documentation
use anyhow::{Context, Result};
use std::fs;
use std::io::Write;
use std::path::PathBuf;
use std::process::{self, Command, Stdio};
use std::sync::mpsc::{self, Receiver};
use std::thread;

use glob::glob;

use adof::get_home_dir;

use super::init::init;
use crate::database::add::add_files;
use crate::git::add::git_add;

use super::*;

pub async fn add() -> Result<()> {
    if !check_for_init().context("Failed to check initialization status")? {
        init().await?;
        process::exit(1);
    }

    let files_to_add = get_files_to_add().context("Failed to retrieve files to add")?;

    if files_to_add.is_empty() {
        println!("Please select new files.");
        process::exit(1);
    }

    create_backup_files(&files_to_add).context("Failed to create backup files")?;
    git_add().context("Failed to add files to git")?;

    println!("Files added successfully.");

    Ok(())
}

fn get_files_to_add() -> Result<Vec<String>> {
    let home_dir = get_home_dir();
    let pattern = format!("{}/**/*", home_dir);

    let (tx, rx) = mpsc::channel();
    thread::spawn(move || {
        for entry in glob(&pattern).expect("Failed to read glob pattern") {
            match entry {
                Ok(path) if path.is_file() => tx.send(path).expect("Failed to send file path"),
                Err(e) => eprintln!("Glob error: {:?}", e),
                _ => (),
            }
        }
    });

    let selected_files = select_files(rx).context("Failed to select files using fzf")?;

    Ok(selected_files
        .into_iter()
        .filter(|file| !is_file_backedup(file).unwrap_or(false))
        .collect())
}

fn select_files(rx: Receiver<PathBuf>) -> Result<Vec<String>> {
    let mut child = Command::new("fzf")
        .arg("--preview")
        .arg("cat {}")
        .arg("-m")
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()
        .context("Failed to start fzf command")?;

    let mut stdin = child.stdin.take().context("Failed to open fzf stdin")?;

    thread::spawn(move || {
        for path in rx.iter() {
            if let Some(file_path) = path.to_str() {
                if writeln!(stdin, "{}", file_path).is_err() {
                    eprintln!("Failed to write file path to fzf stdin");
                }
            }
        }
    });

    let output = child
        .wait_with_output()
        .context("Failed to read fzf output")?;

    Ok(String::from_utf8_lossy(&output.stdout)
        .trim()
        .lines()
        .map(|file| file.to_string())
        .collect())
}

fn create_backup_files(files_to_add: &[String]) -> Result<()> {
    files_to_add.iter().try_for_each(|file_path| {
        let backup_file = create_file(file_path).context("Failed to create backup file")?;
        fs::copy(file_path, &backup_file).context("Failed to copy file to backup location")?;
        add_files(file_path, &backup_file)?;
        Ok(())
    })
}