paru 0.99.2

Aur helper and pacman wrapper
use crate::args::Args;
use crate::config::Config;

use std::ffi::OsStr;
use std::fmt::{Display, Formatter};
use std::path::Path;
use std::process::{Command, Output};
use std::thread;
use std::time::Duration;

use anyhow::{bail, Context, Result};

#[derive(Debug, Clone)]
pub struct PacmanError {
    pub msg: String,
}

impl Display for PacmanError {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.write_str(&self.msg)
    }
}

impl std::error::Error for PacmanError {}

#[derive(Debug, Clone, Copy)]
pub struct Status(i32);

impl Display for Status {
    fn fmt(&self, _f: &mut Formatter<'_>) -> std::fmt::Result {
        Ok(())
    }
}

impl std::error::Error for Status {}

impl Status {
    pub fn code(self) -> i32 {
        self.0
    }

    pub fn success(self) -> Result<i32, Status> {
        if self.0 == 0 {
            Ok(0)
        } else {
            Err(self)
        }
    }
}

pub fn spawn_sudo(sudo: String, flags: Vec<String>) -> Result<()> {
    update_sudo(&sudo, &flags)?;
    thread::spawn(move || sudo_loop(&sudo, &flags));
    Ok(())
}

fn sudo_loop<S: AsRef<OsStr>>(sudo: &str, flags: &[S]) -> Result<()> {
    loop {
        update_sudo(sudo, flags)?;
        thread::sleep(Duration::from_secs(250));
    }
}

fn update_sudo<S: AsRef<OsStr>>(sudo: &str, flags: &[S]) -> Result<()> {
    Command::new(sudo)
        .arg("-v")
        .args(flags)
        .spawn()
        .with_context(|| {
            let flags = flags
                .iter()
                .map(|s| s.as_ref().to_string_lossy().into_owned())
                .collect::<Vec<_>>()
                .join(" ");
            format!("failed to run: {} -v {}", sudo, flags)
        })?
        .wait()?;
    Ok(())
}

pub fn pacman<S: AsRef<str> + Display + std::fmt::Debug>(
    config: &Config,
    args: &Args<S>,
) -> Result<Status> {
    let mut command = if config.need_root {
        let mut command = Command::new(&config.sudo_bin);
        command.args(&config.sudo_flags);
        command.arg(args.bin.as_ref());
        command
    } else {
        Command::new(args.bin.as_ref())
    };

    let ret = command
        .args(args.args())
        .spawn()
        .with_context(|| format!("failed to run: {} {}", args.bin, args.args().join(" ")))?
        .wait()?;
    Ok(Status(ret.code().unwrap_or(1)))
}

pub fn pacman_output<S: AsRef<str> + Display>(config: &Config, args: &Args<S>) -> Result<Output> {
    let mut command = if config.need_root {
        let mut command = Command::new(&config.sudo_bin);
        command.args(&config.sudo_flags);
        command.arg(args.bin.as_ref());
        command
    } else {
        Command::new(args.bin.as_ref())
    };

    let output = command
        .args(args.args())
        .output()
        .with_context(|| format!("failed to run pacman '{}'", args.bin))?;
    Ok(output)
}

pub fn makepkg<S: AsRef<OsStr>>(config: &Config, dir: &Path, args: &[S]) -> Result<Status> {
    let ret = Command::new(&config.makepkg_bin)
        .current_dir(dir)
        .args(&config.mflags)
        .args(args)
        .spawn()
        .with_context(|| {
            format!(
                "failed to run: {} {} {}",
                config.makepkg_bin,
                config.mflags.join(" "),
                args.iter()
                    .map(|a| a.as_ref().to_string_lossy())
                    .collect::<Vec<_>>()
                    .join(" ")
            )
        })?
        .wait()?;

    Ok(Status(ret.code().unwrap_or(1)))
}

pub fn makepkg_output<S: AsRef<OsStr>>(config: &Config, dir: &Path, args: &[S]) -> Result<Output> {
    let ret = Command::new(&config.makepkg_bin)
        .current_dir(dir)
        .args(&config.mflags)
        .args(args)
        .output()
        .with_context(|| {
            format!(
                "failed to run: {} {} {}",
                config.makepkg_bin,
                config.mflags.join(" "),
                args.iter()
                    .map(|a| a.as_ref().to_string_lossy())
                    .collect::<Vec<_>>()
                    .join(" ")
            )
        })?;

    if !ret.status.success() {
        bail!(
            "failed to run: {} {} --verifysource -Ccf: {}",
            config.makepkg_bin,
            config.mflags.join(" "),
            String::from_utf8_lossy(&ret.stderr)
        )
    }

    Ok(ret)
}