use std::fmt::Write as _;
use std::io::{stdout, Write};
use cxx::ExternType;
use crate::config::Config;
use crate::util::{
get_apt_progress_string, terminal_height, terminal_width, time_str, unit_str, NumSys,
};
pub type Worker = raw::Worker;
pub trait AcquireProgress {
fn pulse_interval(&self) -> usize;
fn hit(&mut self, id: u32, description: String);
fn fetch(&mut self, id: u32, description: String, file_size: u64);
fn fail(&mut self, id: u32, description: String, status: u32, error_text: String);
fn pulse(
&mut self,
workers: Vec<Worker>,
percent: f32,
total_bytes: u64,
current_bytes: u64,
current_cps: u64,
);
fn done(&mut self);
fn start(&mut self);
fn stop(
&mut self,
fetched_bytes: u64,
elapsed_time: u64,
current_cps: u64,
pending_errors: bool,
);
}
pub trait OperationProgress {
fn update(&mut self, operation: String, percent: f32);
fn done(&mut self);
}
pub trait InstallProgress {
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);
}
#[derive(Default, Debug)]
pub struct AptAcquireProgress {
lastline: usize,
pulse_interval: usize,
disable: bool,
}
impl AptAcquireProgress {
pub fn new() -> Self { Self::default() }
pub fn new_box() -> Box<dyn AcquireProgress> { Box::new(Self::new()) }
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 AcquireProgress for AptAcquireProgress {
fn pulse_interval(&self) -> usize { self.pulse_interval }
fn hit(&mut self, id: u32, description: String) {
if self.disable {
return;
}
self.clear_last_line(terminal_width() - 1);
println!("\rHit:{} {}", id, description);
}
fn fetch(&mut self, id: u32, description: String, file_size: u64) {
if self.disable {
return;
}
self.clear_last_line(terminal_width() - 1);
if file_size != 0 {
println!(
"\rGet:{id} {description} [{}]",
unit_str(file_size, NumSys::Decimal)
);
} else {
println!("\rGet:{id} {description}");
}
}
fn done(&mut self) {
}
fn start(&mut self) { self.lastline = 0; }
fn stop(
&mut self,
fetched_bytes: u64,
elapsed_time: u64,
current_cps: u64,
pending_errors: bool,
) {
if self.disable {
return;
}
self.clear_last_line(terminal_width() - 1);
if pending_errors {
return;
}
if fetched_bytes != 0 {
println!(
"Fetched {} in {} ({}/s)",
unit_str(fetched_bytes, NumSys::Decimal),
time_str(elapsed_time),
unit_str(current_cps, NumSys::Decimal)
);
} else {
println!("Nothing to fetch.");
}
}
fn fail(&mut self, id: u32, description: String, status: u32, error_text: String) {
if self.disable {
return;
}
self.clear_last_line(terminal_width() - 1);
let mut show_error = true;
if status == 0 || status == 2 {
println!("\rIgn: {id} {description}");
show_error = false;
if error_text.is_empty() {
show_error = false;
}
} else {
println!("\rErr: {id} {description}");
}
if show_error {
println!("\r{error_text}");
}
}
fn pulse(
&mut self,
workers: Vec<Worker>,
percent: f32,
total_bytes: u64,
current_bytes: u64,
current_cps: u64,
) {
if self.disable {
return;
}
let term_width = terminal_width() - 1;
let mut string = String::new();
let mut percent_str = format!("\r{percent:.0}%");
let mut eta_str = String::new();
if current_cps != 0 {
let _ = write!(
eta_str,
" {} {}",
unit_str(current_cps, NumSys::Decimal),
time_str((total_bytes - current_bytes) / current_cps)
);
}
for worker in workers {
let mut work_string = String::new();
work_string.push_str(" [");
if !worker.is_current {
if !worker.status.is_empty() {
work_string.push_str(&worker.status);
work_string.push(']');
}
continue;
}
if worker.id != 0 {
let _ = write!(work_string, " {} ", worker.id);
}
work_string.push_str(&worker.short_desc);
if !worker.active_subprocess.is_empty() {
work_string.push(' ');
work_string.push_str(&worker.active_subprocess);
}
work_string.push(' ');
work_string.push_str(&unit_str(worker.current_size, NumSys::Decimal));
if worker.total_size > 0 && !worker.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 {
#[allow(dead_code)]
pub fn new() -> Self {
Self {
config: Config::new(),
}
}
pub fn new_box() -> Box<dyn InstallProgress> { Box::new(Self::new()) }
}
impl Default for AptInstallProgress {
fn default() -> Self { Self::new() }
}
impl InstallProgress 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[{};0f", term_height);
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!(
"{}{}Progress: [{}%]{}{} ",
bg_color, fg_color, 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) {}
}
#[cxx::bridge]
pub mod raw {
struct Worker {
is_current: bool,
status: String,
id: u64,
short_desc: String,
active_subprocess: String,
current_size: u64,
total_size: u64,
complete: bool,
}
extern "Rust" {
fn pulse_interval(progress: &mut DynAcquireProgress) -> usize;
fn hit(progress: &mut DynAcquireProgress, id: u32, description: String);
fn fetch(progress: &mut DynAcquireProgress, id: u32, description: String, file_size: u64);
fn fail(
progress: &mut DynAcquireProgress,
id: u32,
description: String,
status: u32,
error_text: String,
);
fn pulse(
progress: &mut DynAcquireProgress,
workers: Vec<Worker>,
percent: f32,
total_bytes: u64,
current_bytes: u64,
current_cps: u64,
);
fn done(progress: &mut DynAcquireProgress);
fn start(progress: &mut DynAcquireProgress);
fn stop(
progress: &mut DynAcquireProgress,
fetched_bytes: u64,
elapsed_time: u64,
current_cps: u64,
pending_errors: bool,
);
fn op_update(progress: &mut DynOperationProgress, operation: String, percent: f32);
fn op_done(progress: &mut DynOperationProgress);
fn inst_status_changed(
progress: &mut DynInstallProgress,
pkgname: String,
steps_done: u64,
total_steps: u64,
action: String,
);
fn inst_error(
progress: &mut DynInstallProgress,
pkgname: String,
steps_done: u64,
total_steps: u64,
error: String,
);
}
unsafe extern "C++" {
type DynAcquireProgress = Box<dyn crate::progress::AcquireProgress>;
type DynOperationProgress = Box<dyn crate::progress::OperationProgress>;
type DynInstallProgress = Box<dyn crate::progress::InstallProgress>;
include!("rust-apt/apt-pkg-c/progress.h");
}
}
unsafe impl ExternType for Box<dyn AcquireProgress> {
type Id = cxx::type_id!("DynAcquireProgress");
type Kind = cxx::kind::Trivial;
}
unsafe impl ExternType for Box<dyn OperationProgress> {
type Id = cxx::type_id!("DynOperationProgress");
type Kind = cxx::kind::Trivial;
}
unsafe impl ExternType for Box<dyn InstallProgress> {
type Id = cxx::type_id!("DynInstallProgress");
type Kind = cxx::kind::Trivial;
}
fn pulse_interval(progress: &mut Box<dyn AcquireProgress>) -> usize {
(**progress).pulse_interval()
}
fn hit(progress: &mut Box<dyn AcquireProgress>, id: u32, description: String) {
(**progress).hit(id, description)
}
fn fetch(progress: &mut Box<dyn AcquireProgress>, id: u32, description: String, file_size: u64) {
(**progress).fetch(id, description, file_size)
}
fn fail(
progress: &mut Box<dyn AcquireProgress>,
id: u32,
description: String,
status: u32,
error_text: String,
) {
(**progress).fail(id, description, status, error_text)
}
fn pulse(
progress: &mut Box<dyn AcquireProgress>,
workers: Vec<Worker>,
percent: f32,
total_bytes: u64,
current_bytes: u64,
current_cps: u64,
) {
(**progress).pulse(workers, percent, total_bytes, current_bytes, current_cps)
}
fn done(progress: &mut Box<dyn AcquireProgress>) { (**progress).done() }
fn start(progress: &mut Box<dyn AcquireProgress>) { (**progress).start() }
fn stop(
progress: &mut Box<dyn AcquireProgress>,
fetched_bytes: u64,
elapsed_time: u64,
current_cps: u64,
pending_errors: bool,
) {
(**progress).stop(fetched_bytes, elapsed_time, current_cps, pending_errors)
}
fn op_update(progress: &mut Box<dyn OperationProgress>, operation: String, percent: f32) {
(**progress).update(operation, percent)
}
fn op_done(progress: &mut Box<dyn OperationProgress>) { (**progress).done() }
fn inst_status_changed(
progress: &mut Box<dyn InstallProgress>,
pkgname: String,
steps_done: u64,
total_steps: u64,
action: String,
) {
(**progress).status_changed(pkgname, steps_done, total_steps, action)
}
fn inst_error(
progress: &mut Box<dyn InstallProgress>,
pkgname: String,
steps_done: u64,
total_steps: u64,
error: String,
) {
(**progress).error(pkgname, steps_done, total_steps, error)
}