papa 3.0.0

A cli mod manager for the Northstar launcher
use std::{
    fs,
    path::Path, time::Duration,
};

use crate::{
    config::{CONFIG, DIRS},
    model::{Cache, ModName},
    modfile,
};
use anyhow::{Context, Result};
use indicatif::{ProgressBar, ProgressStyle, ProgressIterator};
use lazy_static::lazy_static;
use owo_colors::OwoColorize;
use regex::Regex;
use thermite::{
    model::ModVersion,
    prelude::{download_with_progress, install_mod},
};
use tracing::debug;

lazy_static! {
    static ref RE: Regex = Regex::new(r"^(\w+)\.(\w+)(?:@(\d+\.\d+\.\d+))?$").unwrap();
}

pub fn validate_modname(input: &str) -> Result<ModName, String> {
    if let Some(captures) = RE.captures(input) {
        let mut name = ModName::default();
        if let Some(author) = captures.get(1) {
            name.author = author.as_str().to_string();
        }

        if let Some(n) = captures.get(2) {
            name.name = n.as_str().to_string();
        }

        name.version = captures.get(3).map(|v| v.as_str().to_string());

        Ok(name)
    } else {
        Err(format!(
            "Mod name '{input}' should be in 'Author.ModName' format"
        ))
    }
}

pub fn to_file_size_string(size: u64) -> String {
    if size / 1_000_000 >= 1 {
        let size = size as f64 / 1_048_576f64;

        format!("{:.2} MB", size)
    } else {
        let size = size as f64 / 1024f64;
        format!("{:.2} KB", size)
    }
}

pub fn ensure_dir(dir: impl AsRef<Path>) -> Result<()> {
    let dir = dir.as_ref();

    debug!("Checking if path '{}' exists", dir.display());
    if !dir.try_exists()? {
        debug!("Path '{}' doesn't exist, creating it", dir.display());
        fs::create_dir_all(dir)?;
    } else {
        debug!("Path '{}' already exists", dir.display());
    }

    Ok(())
}

pub fn download_and_install(
    mods: Vec<(ModName, impl AsRef<ModVersion>)>,
    check_cache: bool,
) -> Result<()> {
    if mods.is_empty() {
        println!("Nothing to do!");
        return Ok(());
    }

    println!("Downloading packages...");
    let mut files = vec![];
    let cache_dir = DIRS.cache_dir();
    ensure_dir(cache_dir)?;
    let cache = Cache::from_dir(cache_dir)?;
    
    for (mn, v) in mods {
        if check_cache {
            if let Some(path) = cache.get(&mn) {
                println!("Using cached version of {}", mn.bright_cyan());
                files.push((mn, modfile!(o, path)?));
                continue;
            }
        }
        let v = v.as_ref();
        // flush!()?;
        let filename = cache.to_cache_path(&mn);
        let pb = ProgressBar::new(v.file_size)
            .with_style(
                ProgressStyle::with_template("{msg}{bar} {bytes}/{total_bytes} {duration}")?
                    .progress_chars(".. "),
            )
            .with_message(format!("Downloading {}", mn.bright_cyan()));
        let mut file = modfile!(filename)?;
        download_with_progress(&mut file, &v.url, |delta, _, _| {
            pb.inc(delta);
        })
        .context(format!("Error downloading {}", mn.red()))?;
        pb.finish();
        files.push((mn, file));
    }
    
    let mut pb = ProgressBar::new_spinner().with_style(ProgressStyle::with_template("{prefix}{msg}\t{spinner}\t{pos}/{len}")?.tick_chars("(|)|\0")).with_prefix("Installing ");
    pb.set_tab_width(1);
    pb.enable_steady_tick(Duration::from_millis(100));
    pb.set_length(files.len() as u64);
    
    for (mn, f) in files.iter().progress_with(pb.clone()){
      
        pb.set_message(format!("{}", mn.bright_cyan()));
        if !CONFIG.is_server() {
            ensure_dir(CONFIG.install_dir())?;
            install_mod(&mn.author,  f, CONFIG.install_dir())?;
            pb.suspend(|| println!("Installed {}", mn.bright_cyan()));
        } else {
            todo!();
        }
    }

    pb.set_prefix("");
    pb.set_tab_width(0);
    pb.finish_with_message("Installed ");
    println!("Done!");
    Ok(())
}