use std::cmp::Ordering;
use std::env;
use crate::error::AptErrors;
use crate::{Cache, DepFlags, Package, config};
fn env_usize_nonzero(key: &str) -> Option<usize> {
env::var(key).ok()?.parse().ok().filter(|v: &usize| *v > 0)
}
fn ioctl_terminal_size() -> Option<(usize, usize)> {
use std::ffi::{c_int, c_ulong};
use std::io::{stderr, stdin, stdout};
use std::mem::MaybeUninit;
use std::os::unix::io::AsRawFd;
#[repr(C)]
struct Winsize {
row: u16,
col: u16,
x_pixel: u16,
y_pixel: u16,
}
extern "C" {
fn ioctl(fd: c_int, request: c_ulong, ...) -> c_int;
}
const TIOCGWINSZ: c_ulong = 0x5413;
fn query(fd: c_int) -> Option<(usize, usize)> {
let mut win_size = MaybeUninit::<Winsize>::uninit();
let result = unsafe { ioctl(fd, TIOCGWINSZ, win_size.as_mut_ptr()) };
if result != 0 {
return None;
}
let win_size = unsafe { win_size.assume_init() };
let cols = usize::from(win_size.col);
let rows = usize::from(win_size.row);
(cols != 0 && rows != 0).then_some((cols, rows))
}
query(stdout().as_raw_fd())
.or_else(|| query(stderr().as_raw_fd()))
.or_else(|| query(stdin().as_raw_fd()))
}
pub fn terminal_height() -> usize {
ioctl_terminal_size()
.map(|(_, rows)| rows)
.or_else(|| env_usize_nonzero("LINES"))
.unwrap_or(24)
}
pub fn terminal_width() -> usize {
ioctl_terminal_size()
.map(|(cols, _)| cols)
.or_else(|| env_usize_nonzero("COLUMNS"))
.unwrap_or(80)
}
pub fn cmp_versions(ver1: &str, ver2: &str) -> Ordering {
let result = raw::cmp_versions(ver1, ver2);
match result {
_ if result < 0 => Ordering::Less,
_ if result == 0 => Ordering::Equal,
_ => Ordering::Greater,
}
}
pub enum DiskSpace {
Require(u64),
Free(u64),
}
pub enum NumSys {
Binary,
Decimal,
}
pub fn unit_str(val: u64, base: NumSys) -> String {
let val = val as f64;
let (num, tera, giga, mega, kilo) = match base {
NumSys::Binary => (1024.0_f64, "TiB", "GiB", "MiB", "KiB"),
NumSys::Decimal => (1000.0_f64, "TB", "GB", "MB", "KB"),
};
let powers = [
(num.powi(4), tera),
(num.powi(3), giga),
(num.powi(2), mega),
(num, kilo),
];
for (divisor, unit) in powers {
if val > divisor {
return format!("{:.2} {unit}", val / divisor);
}
}
format!("{val} B")
}
pub fn time_str(seconds: u64) -> String {
if seconds > 60 * 60 * 24 {
return format!(
"{}d {}h {}min {}s",
seconds / 60 / 60 / 24,
(seconds / 60 / 60) % 24,
(seconds / 60) % 60,
seconds % 60,
);
}
if seconds > 60 * 60 {
return format!(
"{}h {}min {}s",
(seconds / 60 / 60) % 24,
(seconds / 60) % 60,
seconds % 60,
);
}
if seconds > 60 {
return format!("{}min {}s", (seconds / 60) % 60, seconds % 60,);
}
format!("{seconds}s")
}
pub fn get_apt_progress_string(percent: f32, output_width: u32) -> String {
raw::get_apt_progress_string(percent, output_width)
}
pub fn apt_lock() -> Result<(), AptErrors> {
config::init_config_system();
Ok(raw::apt_lock()?)
}
pub fn apt_unlock() {
config::init_config_system();
raw::apt_unlock()
}
pub fn apt_lock_inner() -> Result<(), AptErrors> {
config::init_config_system();
Ok(raw::apt_lock_inner()?)
}
pub fn apt_unlock_inner() {
config::init_config_system();
raw::apt_unlock_inner()
}
pub fn apt_is_locked() -> bool {
config::init_config_system();
raw::apt_is_locked()
}
pub fn show_broken_pkg(cache: &Cache, pkg: &Package, now: bool) -> Option<String> {
if (now && !pkg.is_now_broken()) || (!now && !pkg.is_inst_broken()) {
return None;
};
let mut broken_string = String::new();
broken_string += &format!(" {pkg} :");
let Some(ver) = (match now {
true => pkg.installed(),
false => pkg.install_version(),
}) else {
broken_string += "\n";
return Some(broken_string);
};
let indent = pkg.name().len() + 3;
let mut first = true;
for dep in ver.depends_map().values().flatten() {
for (i, base_dep) in dep.iter().enumerate() {
if !cache.depcache().is_important_dep(base_dep) {
continue;
}
let dep_flag = if now { DepFlags::DepGNow } else { DepFlags::DepInstall };
if cache.depcache().dep_state(base_dep) & dep_flag == dep_flag {
continue;
}
if !first {
broken_string += &" ".repeat(indent);
}
first = false;
if i > 0 {
broken_string += &" ".repeat(base_dep.dep_type().as_ref().len() + 3);
} else {
broken_string += &format!(" {}: ", base_dep.dep_type())
}
broken_string += base_dep.target_package().name();
if let (Ok(ver_str), Some(comp)) = (base_dep.target_ver(), base_dep.comp_type()) {
broken_string += &format!(" ({comp} {ver_str})");
}
let target = base_dep.target_package();
if !target.has_provides() {
if let Some(target_ver) = target.install_version() {
broken_string += &format!(" but {target_ver} is to be installed")
} else if target.candidate().is_some() {
broken_string += " but it is not going to be installed";
} else if target.has_provides() {
broken_string += " but it is a virtual package";
} else {
broken_string += " but it is not installable";
}
}
if i + 1 != dep.len() {
broken_string += " or"
}
broken_string += "\n";
}
}
Some(broken_string)
}
#[cxx::bridge]
pub(crate) mod raw {
unsafe extern "C++" {
include!("rust-apt/apt-pkg-c/util.h");
pub fn cmp_versions(ver1: &str, ver2: &str) -> i32;
pub fn quote_string(string: &str, bad: String) -> String;
pub fn get_apt_progress_string(percent: f32, output_width: u32) -> String;
pub fn apt_lock() -> Result<()>;
pub fn apt_unlock();
pub fn apt_lock_inner() -> Result<()>;
pub fn apt_unlock_inner();
pub fn apt_is_locked() -> bool;
}
}