akg 0.5.4

Fast package manager for Termux
use std::fs::{self, File};
use std::io::{self, Read};
use std::path::{Path, PathBuf};
use std::thread;

use md5::{Digest, Md5};

pub type R<T> = Result<T, String>;

pub const UA: &str = "Termux-PKG/2.0 akg/0.5";
pub const DEFAULT_PREFIX: &str = "/data/data/com.termux/files/usr";

pub fn paint(code: &str, s: &str) -> String {
    format!("\x1b[{code}m{s}\x1b[0m")
}

pub fn hex(b: &[u8]) -> String {
    const H: &[u8; 16] = b"0123456789abcdef";
    let mut s = String::with_capacity(b.len() * 2);
    for x in b {
        s.push(H[(*x >> 4) as usize] as char);
        s.push(H[(*x & 15) as usize] as char);
    }
    s
}

pub fn fmt_bytes(b: f64) -> String {
    if b >= 1_048_576.0 {
        format!("{:.1} MB", b / 1_048_576.0)
    } else if b >= 1024.0 {
        format!("{:.0} KB", b / 1024.0)
    } else {
        format!("{:.0} B", b)
    }
}

pub fn with_suffix(p: &Path, suf: &str) -> PathBuf {
    let mut s = p.as_os_str().to_os_string();
    s.push(suf);
    PathBuf::from(s)
}

pub fn set_mtime(path: &Path, secs: u64) {
    use std::os::unix::ffi::OsStrExt;
    if let Ok(c) = std::ffi::CString::new(path.as_os_str().as_bytes()) {
        let t = libc::timespec {
            tv_sec: secs as libc::time_t,
            tv_nsec: 0,
        };
        let ts = [t, t];
        unsafe {
            libc::utimensat(libc::AT_FDCWD, c.as_ptr(), ts.as_ptr(), 0);
        }
    }
}

pub fn md5_file(p: &Path) -> io::Result<String> {
    let mut f = File::open(p)?;
    let mut h = Md5::new();
    let mut buf = vec![0u8; 65536];
    loop {
        let n = f.read(&mut buf)?;
        if n == 0 {
            break;
        }
        h.update(&buf[..n]);
    }
    Ok(hex(h.finalize().as_slice()))
}

pub fn write_atomic(path: &Path, data: &[u8]) -> R<()> {
    let tmp = with_suffix(path, ".akg-tmp");
    fs::write(&tmp, data).map_err(|e| format!("{}: {e}", tmp.display()))?;
    fs::rename(&tmp, path).map_err(|e| format!("{}: {e}", path.display()))
}

pub fn warn(r: R<()>) {
    if let Err(e) = r {
        eprintln!("{} {}", paint("33", "warning:"), e);
    }
}

pub fn prefix() -> PathBuf {
    PathBuf::from(std::env::var("PREFIX").unwrap_or_else(|_| DEFAULT_PREFIX.into()))
}

pub fn state_dir() -> PathBuf {
    let home = std::env::var("HOME").unwrap_or_else(|_| "/data/data/com.termux/files/home".into());
    PathBuf::from(home).join(".cache/akg")
}

pub fn default_jobs() -> usize {
    let cpus = thread::available_parallelism()
        .map(|n| n.get())
        .unwrap_or(4);
    (cpus * 3).clamp(6, 16)
}

pub fn arch() -> String {
    if let Ok(a) = std::env::var("AKG_ARCH") {
        return a;
    }
    let mut u: libc::utsname = unsafe { std::mem::zeroed() };
    unsafe {
        libc::uname(&mut u);
    }
    let m = unsafe { std::ffi::CStr::from_ptr(u.machine.as_ptr()) }
        .to_string_lossy()
        .into_owned();
    match m.as_str() {
        "aarch64" | "arm64" => "aarch64".to_string(),
        "armv7l" | "armv8l" | "arm" => "arm".to_string(),
        "x86_64" => "x86_64".to_string(),
        "i686" | "i386" => "i686".to_string(),
        other => other.to_string(),
    }
}