use std::fmt::Write as _;
use std::io::{Write, stdout};
use std::os::fd::RawFd;
use std::pin::Pin;
use cxx::{ExternType, UniquePtr};
use crate::config::Config;
use crate::error::raw::pending_error;
use crate::raw::{AcqTextStatus, ItemDesc, ItemState, PkgAcquire, acquire_status};
use crate::util::{
NumSys, get_apt_progress_string, terminal_height, terminal_width, time_str, unit_str,
};
pub trait DynAcquireProgress {
fn pulse_interval(&self) -> usize;
fn hit(&mut self, item: &ItemDesc);
fn fetch(&mut self, item: &ItemDesc);
fn fail(&mut self, item: &ItemDesc);
fn pulse(&mut self, status: &AcqTextStatus, owner: &PkgAcquire);
fn done(&mut self, item: &ItemDesc);
fn start(&mut self);
fn stop(&mut self, status: &AcqTextStatus);
}
pub trait DynOperationProgress {
fn update(&mut self, operation: String, percent: f32);
fn done(&mut self);
}
pub trait DynInstallProgress {
fn status_changed(
&mut self,
pkgname: String,
steps_done: u64,
total_steps: u64,
action: String,
);
fn error(&mut self, pkgname: String, steps_done: u64, total_steps: u64, error: String);
}
pub struct AcquireProgress<'a> {
status: UniquePtr<AcqTextStatus>,
inner: Box<dyn DynAcquireProgress + 'a>,
}
impl<'a> AcquireProgress<'a> {
pub fn new(inner: impl DynAcquireProgress + 'a) -> Self {
Self {
status: unsafe { acquire_status() },
inner: Box::new(inner),
}
}
pub fn apt() -> Self { Self::new(AptAcquireProgress::new()) }
pub fn quiet() -> Self { Self::new(AptAcquireProgress::disable()) }
pub fn mut_status(&mut self) -> Pin<&mut AcqTextStatus> {
unsafe {
let raw_ptr = &mut *(self as *mut AcquireProgress);
let mut status = self.status.pin_mut();
status.as_mut().set_callback(raw_ptr);
status
}
}
pub(crate) fn pulse_interval(&mut self) -> usize { self.inner.pulse_interval() }
pub(crate) fn hit(&mut self, item: &ItemDesc) { self.inner.hit(item) }
pub(crate) fn fetch(&mut self, item: &ItemDesc) { self.inner.fetch(item) }
pub(crate) fn fail(&mut self, item: &ItemDesc) { self.inner.fail(item) }
pub(crate) fn pulse(&mut self, owner: &PkgAcquire) { self.inner.pulse(&self.status, owner) }
pub(crate) fn start(&mut self) { self.inner.start() }
pub(crate) fn done(&mut self, item: &ItemDesc) { self.inner.done(item) }
pub(crate) fn stop(&mut self) { self.inner.stop(&self.status) }
}
impl Default for AcquireProgress<'_> {
fn default() -> Self { Self::apt() }
}
unsafe impl ExternType for AcquireProgress<'_> {
type Id = cxx::type_id!("AcquireProgress");
type Kind = cxx::kind::Trivial;
}
pub struct OperationProgress<'a> {
inner: Box<dyn DynOperationProgress + 'a>,
}
impl<'a> OperationProgress<'a> {
pub fn new(inner: impl DynOperationProgress + 'static) -> Self {
Self {
inner: Box::new(inner),
}
}
pub fn quiet() -> Self { Self::new(NoOpProgress {}) }
fn update(&mut self, operation: String, percent: f32) { self.inner.update(operation, percent) }
fn done(&mut self) { self.inner.done() }
pub fn pin(&mut self) -> Pin<&mut OperationProgress<'a>> { Pin::new(self) }
}
impl Default for OperationProgress<'_> {
fn default() -> Self { Self::quiet() }
}
unsafe impl ExternType for OperationProgress<'_> {
type Id = cxx::type_id!("OperationProgress");
type Kind = cxx::kind::Trivial;
}
pub enum InstallProgress<'a> {
Fancy(InstallProgressFancy<'a>),
Fd(RawFd),
}
impl InstallProgress<'_> {
pub fn new(inner: impl DynInstallProgress + 'static) -> Self {
Self::Fancy(InstallProgressFancy::new(inner))
}
pub fn fd(fd: RawFd) -> Self { Self::Fd(fd) }
pub fn apt() -> Self { Self::new(AptInstallProgress::new()) }
}
impl Default for InstallProgress<'_> {
fn default() -> Self { Self::apt() }
}
pub struct InstallProgressFancy<'a> {
inner: Box<dyn DynInstallProgress + 'a>,
}
impl<'a> InstallProgressFancy<'a> {
pub fn new(inner: impl DynInstallProgress + 'static) -> Self {
Self {
inner: Box::new(inner),
}
}
pub fn apt() -> Self { Self::new(AptInstallProgress::new()) }
fn status_changed(
&mut self,
pkgname: String,
steps_done: u64,
total_steps: u64,
action: String,
) {
self.inner
.status_changed(pkgname, steps_done, total_steps, action)
}
fn error(&mut self, pkgname: String, steps_done: u64, total_steps: u64, error: String) {
self.inner.error(pkgname, steps_done, total_steps, error)
}
pub fn pin(&mut self) -> Pin<&mut InstallProgressFancy<'a>> { Pin::new(self) }
}
impl Default for InstallProgressFancy<'_> {
fn default() -> Self { Self::apt() }
}
unsafe impl ExternType for InstallProgressFancy<'_> {
type Id = cxx::type_id!("InstallProgressFancy");
type Kind = cxx::kind::Trivial;
}
struct NoOpProgress {}
impl DynOperationProgress for NoOpProgress {
fn update(&mut self, _operation: String, _percent: f32) {}
fn done(&mut self) {}
}
#[derive(Default, Debug)]
pub struct AptAcquireProgress {
lastline: usize,
pulse_interval: usize,
disable: bool,
config: Config,
}
impl AptAcquireProgress {
pub fn new() -> Self { Self::default() }
pub fn disable() -> Self {
AptAcquireProgress {
disable: true,
..Default::default()
}
}
fn clear_last_line(&mut self, term_width: usize) {
if self.disable {
return;
}
if self.lastline == 0 {
return;
}
if self.lastline > term_width {
self.lastline = term_width
}
print!("\r{}", " ".repeat(self.lastline));
print!("\r");
stdout().flush().unwrap();
}
}
impl DynAcquireProgress for AptAcquireProgress {
fn pulse_interval(&self) -> usize { self.pulse_interval }
fn hit(&mut self, item: &ItemDesc) {
if self.disable {
return;
}
self.clear_last_line(terminal_width() - 1);
println!("\rHit:{} {}", item.owner().id(), item.description());
}
fn fetch(&mut self, item: &ItemDesc) {
if self.disable {
return;
}
self.clear_last_line(terminal_width() - 1);
let mut string = format!("\rGet:{} {}", item.owner().id(), item.description());
let file_size = item.owner().file_size();
if file_size != 0 {
string.push_str(&format!(" [{}]", unit_str(file_size, NumSys::Decimal)));
}
println!("{string}");
}
fn done(&mut self, _item: &ItemDesc) {
}
fn start(&mut self) { self.lastline = 0; }
fn stop(&mut self, owner: &AcqTextStatus) {
if self.disable {
return;
}
self.clear_last_line(terminal_width() - 1);
if pending_error() {
return;
}
if owner.fetched_bytes() != 0 {
println!(
"Fetched {} in {} ({}/s)",
unit_str(owner.fetched_bytes(), NumSys::Decimal),
time_str(owner.elapsed_time()),
unit_str(owner.current_cps(), NumSys::Decimal)
);
} else {
println!("Nothing to fetch.");
}
}
fn fail(&mut self, item: &ItemDesc) {
if self.disable {
return;
}
self.clear_last_line(terminal_width() - 1);
let mut show_error = true;
let error_text = item.owner().error_text();
let desc = format!("{} {}", item.owner().id(), item.description());
match item.owner().status() {
ItemState::StatIdle | ItemState::StatDone => {
println!("\rIgn: {desc}");
let key = "Acquire::Progress::Ignore::ShowErrorText";
if error_text.is_empty() || self.config.bool(key, false) {
show_error = false;
}
},
_ => {
println!("\rErr: {desc}");
},
}
if show_error {
println!("\r{error_text}");
}
}
fn pulse(&mut self, status: &AcqTextStatus, owner: &PkgAcquire) {
if self.disable {
return;
}
let term_width = terminal_width() - 1;
let mut string = String::new();
let mut percent_str = format!("\r{:.0}%", status.percent());
let mut eta_str = String::new();
let current_cps = status.current_cps();
if current_cps != 0 {
let _ = write!(
eta_str,
" {} {}",
unit_str(current_cps, NumSys::Decimal),
time_str((status.total_bytes() - status.current_bytes()) / current_cps)
);
}
for worker in owner.workers().iter() {
let mut work_string = String::new();
work_string.push_str(" [");
let Ok(item) = worker.item() else {
if !worker.status().is_empty() {
work_string.push_str(&worker.status());
work_string.push(']');
}
continue;
};
let id = item.owner().id();
if id != 0 {
let _ = write!(work_string, " {id} ");
}
work_string.push_str(&item.short_desc());
let sub = item.owner().active_subprocess();
if !sub.is_empty() {
work_string.push(' ');
work_string.push_str(&sub);
}
work_string.push(' ');
work_string.push_str(&unit_str(worker.current_size(), NumSys::Decimal));
if worker.total_size() > 0 && !item.owner().complete() {
let _ = write!(
work_string,
"/{} {}%",
unit_str(worker.total_size(), NumSys::Decimal),
(worker.current_size() * 100) / worker.total_size()
);
}
work_string.push(']');
if (string.len() + work_string.len() + percent_str.len() + eta_str.len()) > term_width {
break;
}
string.push_str(&work_string);
}
if string.is_empty() {
string = " [Working]".to_string()
}
percent_str.push_str(&string);
if !eta_str.is_empty() {
let fill_size = percent_str.len() + eta_str.len();
if fill_size < term_width {
percent_str.push_str(&" ".repeat(term_width - fill_size))
}
}
percent_str.push_str(&eta_str);
print!("{percent_str}");
stdout().flush().unwrap();
if self.lastline > percent_str.len() {
self.clear_last_line(term_width);
}
self.lastline = percent_str.len();
}
}
pub struct AptInstallProgress {
config: Config,
}
impl AptInstallProgress {
pub fn new() -> Self {
Self {
config: Config::new(),
}
}
}
impl Default for AptInstallProgress {
fn default() -> Self { Self::new() }
}
impl DynInstallProgress for AptInstallProgress {
fn status_changed(
&mut self,
_pkgname: String,
steps_done: u64,
total_steps: u64,
_action: String,
) {
let term_height = terminal_height();
let term_width = terminal_width();
print!("\x1b7");
print!("\x1b[{term_height};0f");
std::io::stdout().flush().unwrap();
let percent = steps_done as f32 / total_steps as f32;
let mut percent_str = (percent * 100.0).round().to_string();
let percent_padding = match percent_str.len() {
1 => " ",
2 => " ",
3 => "",
_ => unreachable!(),
};
percent_str = percent_padding.to_owned() + &percent_str;
let bg_color = self
.config
.find("Dpkg::Progress-Fancy::Progress-fg", "\x1b[42m");
let fg_color = self
.config
.find("Dpkg::Progress-Fancy::Progress-bg", "\x1b[30m");
const BG_COLOR_RESET: &str = "\x1b[49m";
const FG_COLOR_RESET: &str = "\x1b[39m";
print!("{bg_color}{fg_color}Progress: [{percent_str}%]{BG_COLOR_RESET}{FG_COLOR_RESET} ");
const PROGRESS_STR_LEN: usize = 17;
print!(
"{}",
get_apt_progress_string(percent, (term_width - PROGRESS_STR_LEN).try_into().unwrap())
);
std::io::stdout().flush().unwrap();
print!("\x1b8");
std::io::stdout().flush().unwrap();
}
fn error(&mut self, _pkgname: String, _steps_done: u64, _total_steps: u64, _error: String) {}
}
#[allow(clippy::needless_lifetimes)]
#[cxx::bridge]
pub(crate) mod raw {
extern "Rust" {
type AcquireProgress<'a>;
type OperationProgress<'a>;
type InstallProgressFancy<'a>;
fn update(self: &mut OperationProgress, operation: String, percent: f32);
fn done(self: &mut OperationProgress);
fn status_changed(
self: &mut InstallProgressFancy,
pkgname: String,
steps_done: u64,
total_steps: u64,
action: String,
);
fn error(
self: &mut InstallProgressFancy,
pkgname: String,
steps_done: u64,
total_steps: u64,
error: String,
);
fn pulse_interval(self: &mut AcquireProgress) -> usize;
fn hit(self: &mut AcquireProgress, item: &ItemDesc);
fn fetch(self: &mut AcquireProgress, item: &ItemDesc);
fn fail(self: &mut AcquireProgress, item: &ItemDesc);
fn pulse(self: &mut AcquireProgress, owner: &PkgAcquire);
fn done(self: &mut AcquireProgress, item: &ItemDesc);
fn start(self: &mut AcquireProgress);
fn stop(self: &mut AcquireProgress);
}
extern "C++" {
type ItemDesc = crate::acquire::raw::ItemDesc;
type PkgAcquire = crate::acquire::raw::PkgAcquire;
include!("rust-apt/apt-pkg-c/types.h");
}
}