paru 0.99.2

Aur helper and pacman wrapper
use crate::config::Config;
use crate::print_error;

use std::fs::{metadata, OpenOptions};
use std::io::{stdout, BufRead, BufReader, Write};
use std::path::Path;
use std::time::{Duration, SystemTime};

use anyhow::{ensure, Context, Result};
use libflate::gzip::Decoder;
use reqwest::blocking::get;
use url::Url;

fn save_aur_list(aur_url: &Url, cache_dir: &Path) -> Result<()> {
    let url = aur_url.join("packages.gz")?;
    let resp = get(url.clone()).with_context(|| format!("get {}", url))?;
    let success = resp.status().is_success();
    ensure!(success, "get {}: {}", url, resp.status());

    let data = resp.bytes()?;
    let decoder = Decoder::new(&data[..]).context("invalid gzip data")?;
    let decoder = BufReader::new(decoder);

    let path = cache_dir.join("packages.aur");
    let file = OpenOptions::new().write(true).create(true).open(&path);
    let mut file =
        file.with_context(|| format!("failed to open cache file '{}'", path.display()))?;

    for line in decoder.split(b'\n').skip(1) {
        let line = line?;
        if !line.is_empty() {
            file.write_all(&line)?;
            file.write_all(b"\n")?;
        }
    }

    Ok(())
}

pub fn update_aur_cache(aur_url: &Url, cache_dir: &Path, timeout: Option<u64>) -> Result<()> {
    let path = cache_dir.join("packages.aur");
    let metadata = metadata(&path);

    let need_refresh = match metadata {
        Err(err) if err.kind() == std::io::ErrorKind::NotFound => true,
        Err(err) => return Err(anyhow::Error::new(err)),
        Ok(metadate) => match timeout {
            Some(timeout) => {
                metadate.modified()?
                    > SystemTime::now() + Duration::from_secs(60 * 60 * 24 * timeout)
            }
            None => false,
        },
    };

    if need_refresh {
        save_aur_list(aur_url, cache_dir)?;
    }

    Ok(())
}

fn aur_list<W: Write>(config: &Config, w: &mut W, timeout: Option<u64>) -> Result<()> {
    update_aur_cache(&config.aur_url, &config.cache_dir, timeout)
        .context("could not update aur cache")?;
    let path = config.cache_dir.join("packages.aur");
    let file = OpenOptions::new().read(true).open(path)?;
    let file = BufReader::new(file);

    for line in file.split(b'\n') {
        let _ = w.write_all(&line?);
        let _ = w.write_all(b" AUR\n");
    }

    Ok(())
}

fn repo_list<W: Write>(config: &Config, w: &mut W) -> Result<()> {
    for db in config.alpm.syncdbs() {
        for pkg in db.pkgs()? {
            let _ = w.write_all(pkg.name().as_bytes());
            let _ = w.write_all(b" ");
            let _ = w.write_all(db.name().as_bytes());
            let _ = w.write_all(b"\n");
        }
    }

    Ok(())
}

pub fn print(config: &Config, timeout: Option<u64>) -> i32 {
    let stdout = stdout();
    let mut stdout = stdout.lock();
    let mut ret = 0;

    if let Err(err) = repo_list(config, &mut stdout) {
        ret = 1;
        print_error(config.color.error, err);
    }

    if let Err(err) = aur_list(config, &mut stdout, timeout) {
        ret = 1;
        print_error(config.color.error, err);
    }

    ret
}