nedots 0.1.3

A tool to manage configuration files/(ne)dots.
use crate::paths::MakeDirs;
use dialoguer::{theme::ColorfulTheme, Select};
use std::path::Path;

#[derive(Debug, clap::Args)]
/// Restore a backup - decompress archive if compressed, then distribute files
/// to their original local location.
pub(crate) struct RestoreCmd {
    #[arg(short, long)]
    /// Don't bother backing up.
    no_backup: bool,
}

impl super::Command for RestoreCmd {
    fn run(
        &self,
        _: &super::nedots::RootCmd,
        config: &crate::config::Config,
    ) -> anyhow::Result<()> {
        // Read backup directories - filter errors, and return Strings.
        let mut items: Vec<String> = config
            .root
            .join(&config.backup_dir)
            .read_dir()?
            .map(|e| e.unwrap().path().display().to_string())
            .collect();

        // Place the latest backup at the top of the list - when we set default
        // selection to index 0, this is the latest backup.
        items.reverse();

        if items.len().gt(&0) {
            if !self.no_backup {
                super::backup::backup(
                    &config.sources,
                    &config
                        .root
                        .join(&config.backup_dir)
                        .join(super::backup::get_timestamp()),
                    true,
                )?;
            }

            let selection = Select::with_theme(&ColorfulTheme::default())
                .items(&items)
                .default(0)
                .interact_on_opt(&console::Term::stderr())?;

            if let Some(index) = selection {
                let path = Path::new(&items[index]);
                let file = std::fs::File::open(path)?;
                let mut archive = zip::ZipArchive::new(file)?;

                for i in 0..archive.len() {
                    let mut file = archive.by_index(i)?;
                    let dst = path.with_extension("").join(file.enclosed_name().unwrap());

                    if (*file.name()).ends_with('/') {
                        log::debug!("File {} extracted to {}", i, dst.display());
                        dst.make_all_dirs()?;
                    } else {
                        log::debug!(
                            "File {} extracted to {} ({} bytes)",
                            i,
                            dst.display(),
                            file.size()
                        );
                        if let Some(p) = dst.parent() {
                            if !p.exists() {
                                p.make_all_dirs()?;
                            }
                        }
                        let mut outfile = std::fs::File::create(&dst)?;
                        std::io::copy(&mut file, &mut outfile)?;
                    }

                    #[cfg(unix)]
                    {
                        use std::os::unix::prelude::PermissionsExt;

                        if let Some(mode) = file.unix_mode() {
                            std::fs::set_permissions(path, std::fs::Permissions::from_mode(mode))?;
                            log::debug!(
                                "Set {} permissions to {}",
                                path.display(),
                                std::fs::Permissions::from_mode(mode).mode()
                            )
                        }
                    }
                }

                log::info!(
                    "🛠️ Extracted {}",
                    console::style(path.with_extension("").display())
                        .green()
                        .bold()
                );

                let src = path.with_extension("");
                for entry in src.read_dir()? {
                    let path = entry?.path();
                    let dst = path
                        .display()
                        .to_string()
                        .replace(&src.display().to_string(), "");

                    super::gather::gather_file(&path, Path::new(&dst))?;
                    log::info!(
                        "✅ Restored {}",
                        console::style(path.display()).green().bold()
                    );
                }

                log::trace!("Tidying up! Removing {}", path.display());
                trash::delete_all([path, &path.with_extension("")])?;
            }
        } else {
            log::warn!("No backups to restore");
            return Ok(());
        }

        Ok(())
    }
}