1use std::fmt::Write as _;
3use std::io::{Write, stdout};
4use std::os::fd::RawFd;
5use std::pin::Pin;
6
7use cxx::{ExternType, UniquePtr};
8
9use crate::config::Config;
10use crate::error::raw::pending_error;
11use crate::raw::{AcqTextStatus, ItemDesc, ItemState, PkgAcquire, acquire_status};
12use crate::util::{
13 NumSys, get_apt_progress_string, terminal_height, terminal_width, time_str, unit_str,
14};
15
16pub trait DynAcquireProgress {
18 fn pulse_interval(&self) -> usize;
20
21 fn hit(&mut self, item: &ItemDesc);
23
24 fn fetch(&mut self, item: &ItemDesc);
26
27 fn fail(&mut self, item: &ItemDesc);
29
30 fn pulse(&mut self, status: &AcqTextStatus, owner: &PkgAcquire);
32
33 fn done(&mut self, item: &ItemDesc);
35
36 fn start(&mut self);
38
39 fn stop(&mut self, status: &AcqTextStatus);
41}
42
43pub trait DynOperationProgress {
45 fn update(&mut self, operation: String, percent: f32);
46 fn done(&mut self);
47}
48
49pub trait DynInstallProgress {
51 fn status_changed(
52 &mut self,
53 pkgname: String,
54 steps_done: u64,
55 total_steps: u64,
56 action: String,
57 );
58 fn error(&mut self, pkgname: String, steps_done: u64, total_steps: u64, error: String);
59}
60
61pub struct AcquireProgress<'a> {
69 status: UniquePtr<AcqTextStatus>,
70 inner: Box<dyn DynAcquireProgress + 'a>,
71}
72
73impl<'a> AcquireProgress<'a> {
74 pub fn new(inner: impl DynAcquireProgress + 'a) -> Self {
77 Self {
78 status: unsafe { acquire_status() },
79 inner: Box::new(inner),
80 }
81 }
82
83 pub fn apt() -> Self { Self::new(AptAcquireProgress::new()) }
86
87 pub fn quiet() -> Self { Self::new(AptAcquireProgress::disable()) }
89
90 pub fn mut_status(&mut self) -> Pin<&mut AcqTextStatus> {
93 unsafe {
94 let raw_ptr = &mut *(self as *mut AcquireProgress);
96 let mut status = self.status.pin_mut();
99
100 status.as_mut().set_callback(raw_ptr);
107 status
108 }
109 }
110
111 pub(crate) fn pulse_interval(&mut self) -> usize { self.inner.pulse_interval() }
113
114 pub(crate) fn hit(&mut self, item: &ItemDesc) { self.inner.hit(item) }
116
117 pub(crate) fn fetch(&mut self, item: &ItemDesc) { self.inner.fetch(item) }
119
120 pub(crate) fn fail(&mut self, item: &ItemDesc) { self.inner.fail(item) }
122
123 pub(crate) fn pulse(&mut self, owner: &PkgAcquire) { self.inner.pulse(&self.status, owner) }
125
126 pub(crate) fn start(&mut self) { self.inner.start() }
128
129 pub(crate) fn done(&mut self, item: &ItemDesc) { self.inner.done(item) }
131
132 pub(crate) fn stop(&mut self) { self.inner.stop(&self.status) }
134}
135
136impl Default for AcquireProgress<'_> {
137 fn default() -> Self { Self::apt() }
138}
139
140unsafe impl ExternType for AcquireProgress<'_> {
142 type Id = cxx::type_id!("AcquireProgress");
143 type Kind = cxx::kind::Trivial;
144}
145
146pub struct OperationProgress<'a> {
151 inner: Box<dyn DynOperationProgress + 'a>,
152}
153
154impl<'a> OperationProgress<'a> {
155 pub fn new(inner: impl DynOperationProgress + 'static) -> Self {
158 Self {
159 inner: Box::new(inner),
160 }
161 }
162
163 pub fn quiet() -> Self { Self::new(NoOpProgress {}) }
167
168 fn update(&mut self, operation: String, percent: f32) { self.inner.update(operation, percent) }
170
171 fn done(&mut self) { self.inner.done() }
173
174 pub fn pin(&mut self) -> Pin<&mut OperationProgress<'a>> { Pin::new(self) }
175}
176
177impl Default for OperationProgress<'_> {
178 fn default() -> Self { Self::quiet() }
179}
180
181unsafe impl ExternType for OperationProgress<'_> {
183 type Id = cxx::type_id!("OperationProgress");
184 type Kind = cxx::kind::Trivial;
185}
186
187pub enum InstallProgress<'a> {
191 Fancy(InstallProgressFancy<'a>),
192 Fd(RawFd),
193}
194
195impl InstallProgress<'_> {
196 pub fn new(inner: impl DynInstallProgress + 'static) -> Self {
199 Self::Fancy(InstallProgressFancy::new(inner))
200 }
201
202 pub fn fd(fd: RawFd) -> Self { Self::Fd(fd) }
205
206 pub fn apt() -> Self { Self::new(AptInstallProgress::new()) }
208}
209
210impl Default for InstallProgress<'_> {
211 fn default() -> Self { Self::apt() }
212}
213
214pub struct InstallProgressFancy<'a> {
218 inner: Box<dyn DynInstallProgress + 'a>,
219}
220
221impl<'a> InstallProgressFancy<'a> {
222 pub fn new(inner: impl DynInstallProgress + 'static) -> Self {
225 Self {
226 inner: Box::new(inner),
227 }
228 }
229
230 pub fn apt() -> Self { Self::new(AptInstallProgress::new()) }
232
233 fn status_changed(
234 &mut self,
235 pkgname: String,
236 steps_done: u64,
237 total_steps: u64,
238 action: String,
239 ) {
240 self.inner
241 .status_changed(pkgname, steps_done, total_steps, action)
242 }
243
244 fn error(&mut self, pkgname: String, steps_done: u64, total_steps: u64, error: String) {
245 self.inner.error(pkgname, steps_done, total_steps, error)
246 }
247
248 pub fn pin(&mut self) -> Pin<&mut InstallProgressFancy<'a>> { Pin::new(self) }
249}
250
251impl Default for InstallProgressFancy<'_> {
252 fn default() -> Self { Self::apt() }
253}
254
255unsafe impl ExternType for InstallProgressFancy<'_> {
257 type Id = cxx::type_id!("InstallProgressFancy");
258 type Kind = cxx::kind::Trivial;
259}
260
261struct NoOpProgress {}
267
268impl DynOperationProgress for NoOpProgress {
269 fn update(&mut self, _operation: String, _percent: f32) {}
270
271 fn done(&mut self) {}
272}
273
274#[derive(Default, Debug)]
278pub struct AptAcquireProgress {
279 lastline: usize,
280 pulse_interval: usize,
281 disable: bool,
282 config: Config,
283}
284
285impl AptAcquireProgress {
286 pub fn new() -> Self { Self::default() }
288
289 pub fn disable() -> Self {
291 AptAcquireProgress {
292 disable: true,
293 ..Default::default()
294 }
295 }
296
297 fn clear_last_line(&mut self, term_width: usize) {
299 if self.disable {
300 return;
301 }
302
303 if self.lastline == 0 {
304 return;
305 }
306
307 if self.lastline > term_width {
308 self.lastline = term_width
309 }
310
311 print!("\r{}", " ".repeat(self.lastline));
312 print!("\r");
313 stdout().flush().unwrap();
314 }
315}
316
317impl DynAcquireProgress for AptAcquireProgress {
318 fn pulse_interval(&self) -> usize { self.pulse_interval }
330
331 fn hit(&mut self, item: &ItemDesc) {
335 if self.disable {
336 return;
337 }
338
339 self.clear_last_line(terminal_width() - 1);
340
341 println!("\rHit:{} {}", item.owner().id(), item.description());
342 }
343
344 fn fetch(&mut self, item: &ItemDesc) {
348 if self.disable {
349 return;
350 }
351
352 self.clear_last_line(terminal_width() - 1);
353
354 let mut string = format!("\rGet:{} {}", item.owner().id(), item.description());
355
356 let file_size = item.owner().file_size();
357 if file_size != 0 {
358 string.push_str(&format!(" [{}]", unit_str(file_size, NumSys::Decimal)));
359 }
360
361 println!("{string}");
362 }
363
364 fn done(&mut self, _item: &ItemDesc) {
368 }
372
373 fn start(&mut self) { self.lastline = 0; }
380
381 fn stop(&mut self, owner: &AcqTextStatus) {
387 if self.disable {
388 return;
389 }
390
391 self.clear_last_line(terminal_width() - 1);
392
393 if pending_error() {
394 return;
395 }
396
397 if owner.fetched_bytes() != 0 {
398 println!(
399 "Fetched {} in {} ({}/s)",
400 unit_str(owner.fetched_bytes(), NumSys::Decimal),
401 time_str(owner.elapsed_time()),
402 unit_str(owner.current_cps(), NumSys::Decimal)
403 );
404 } else {
405 println!("Nothing to fetch.");
406 }
407 }
408
409 fn fail(&mut self, item: &ItemDesc) {
413 if self.disable {
414 return;
415 }
416
417 self.clear_last_line(terminal_width() - 1);
418
419 let mut show_error = true;
420 let error_text = item.owner().error_text();
421 let desc = format!("{} {}", item.owner().id(), item.description());
422
423 match item.owner().status() {
424 ItemState::StatIdle | ItemState::StatDone => {
425 println!("\rIgn: {desc}");
426 let key = "Acquire::Progress::Ignore::ShowErrorText";
427 if error_text.is_empty() || self.config.bool(key, false) {
428 show_error = false;
429 }
430 },
431 _ => {
432 println!("\rErr: {desc}");
433 },
434 }
435
436 if show_error {
437 println!("\r{error_text}");
438 }
439 }
440
441 fn pulse(&mut self, status: &AcqTextStatus, owner: &PkgAcquire) {
447 if self.disable {
448 return;
449 }
450
451 let term_width = terminal_width() - 1;
453
454 let mut string = String::new();
455 let mut percent_str = format!("\r{:.0}%", status.percent());
456 let mut eta_str = String::new();
457
458 let current_cps = status.current_cps();
460 if current_cps != 0 {
461 let _ = write!(
462 eta_str,
463 " {} {}",
464 unit_str(current_cps, NumSys::Decimal),
466 time_str((status.total_bytes() - status.current_bytes()) / current_cps)
468 );
469 }
470
471 for worker in owner.workers().iter() {
472 let mut work_string = String::new();
473 work_string.push_str(" [");
474
475 let Ok(item) = worker.item() else {
476 if !worker.status().is_empty() {
477 work_string.push_str(&worker.status());
478 work_string.push(']');
479 }
480 continue;
481 };
482
483 let id = item.owner().id();
484 if id != 0 {
485 let _ = write!(work_string, " {id} ");
486 }
487 work_string.push_str(&item.short_desc());
488
489 let sub = item.owner().active_subprocess();
490 if !sub.is_empty() {
491 work_string.push(' ');
492 work_string.push_str(&sub);
493 }
494
495 work_string.push(' ');
496 work_string.push_str(&unit_str(worker.current_size(), NumSys::Decimal));
497
498 if worker.total_size() > 0 && !item.owner().complete() {
499 let _ = write!(
500 work_string,
501 "/{} {}%",
502 unit_str(worker.total_size(), NumSys::Decimal),
503 (worker.current_size() * 100) / worker.total_size()
504 );
505 }
506
507 work_string.push(']');
508
509 if (string.len() + work_string.len() + percent_str.len() + eta_str.len()) > term_width {
510 break;
511 }
512
513 string.push_str(&work_string);
514 }
515
516 if string.is_empty() {
518 string = " [Working]".to_string()
519 }
520
521 percent_str.push_str(&string);
523
524 if !eta_str.is_empty() {
526 let fill_size = percent_str.len() + eta_str.len();
527 if fill_size < term_width {
528 percent_str.push_str(&" ".repeat(term_width - fill_size))
529 }
530 }
531
532 percent_str.push_str(&eta_str);
534
535 print!("{percent_str}");
537 stdout().flush().unwrap();
538
539 if self.lastline > percent_str.len() {
540 self.clear_last_line(term_width);
541 }
542
543 self.lastline = percent_str.len();
544 }
545}
546
547pub struct AptInstallProgress {
549 config: Config,
550}
551
552impl AptInstallProgress {
553 pub fn new() -> Self {
554 Self {
555 config: Config::new(),
556 }
557 }
558}
559
560impl Default for AptInstallProgress {
561 fn default() -> Self { Self::new() }
562}
563
564impl DynInstallProgress for AptInstallProgress {
565 fn status_changed(
566 &mut self,
567 _pkgname: String,
568 steps_done: u64,
569 total_steps: u64,
570 _action: String,
571 ) {
572 let term_height = terminal_height();
574 let term_width = terminal_width();
575
576 print!("\x1b7");
578
579 print!("\x1b[{term_height};0f");
581 std::io::stdout().flush().unwrap();
582
583 let percent = steps_done as f32 / total_steps as f32;
585 let mut percent_str = (percent * 100.0).round().to_string();
586
587 let percent_padding = match percent_str.len() {
588 1 => " ",
589 2 => " ",
590 3 => "",
591 _ => unreachable!(),
592 };
593
594 percent_str = percent_padding.to_owned() + &percent_str;
595
596 let bg_color = self
600 .config
601 .find("Dpkg::Progress-Fancy::Progress-fg", "\x1b[42m");
602 let fg_color = self
603 .config
604 .find("Dpkg::Progress-Fancy::Progress-bg", "\x1b[30m");
605 const BG_COLOR_RESET: &str = "\x1b[49m";
606 const FG_COLOR_RESET: &str = "\x1b[39m";
607
608 print!("{bg_color}{fg_color}Progress: [{percent_str}%]{BG_COLOR_RESET}{FG_COLOR_RESET} ");
609
610 const PROGRESS_STR_LEN: usize = 17;
612
613 print!(
618 "{}",
619 get_apt_progress_string(percent, (term_width - PROGRESS_STR_LEN).try_into().unwrap())
620 );
621 std::io::stdout().flush().unwrap();
622
623 print!("\x1b8");
630 std::io::stdout().flush().unwrap();
631 }
632
633 fn error(&mut self, _pkgname: String, _steps_done: u64, _total_steps: u64, _error: String) {}
635}
636
637#[allow(clippy::needless_lifetimes)]
638#[cxx::bridge]
639pub(crate) mod raw {
640 extern "Rust" {
641 type AcquireProgress<'a>;
642 type OperationProgress<'a>;
643 type InstallProgressFancy<'a>;
644
645 fn update(self: &mut OperationProgress, operation: String, percent: f32);
647
648 fn done(self: &mut OperationProgress);
650
651 fn status_changed(
653 self: &mut InstallProgressFancy,
654 pkgname: String,
655 steps_done: u64,
656 total_steps: u64,
657 action: String,
658 );
659
660 fn error(
664 self: &mut InstallProgressFancy,
665 pkgname: String,
666 steps_done: u64,
667 total_steps: u64,
668 error: String,
669 );
670
671 fn pulse_interval(self: &mut AcquireProgress) -> usize;
673
674 fn hit(self: &mut AcquireProgress, item: &ItemDesc);
676
677 fn fetch(self: &mut AcquireProgress, item: &ItemDesc);
679
680 fn fail(self: &mut AcquireProgress, item: &ItemDesc);
682
683 fn pulse(self: &mut AcquireProgress, owner: &PkgAcquire);
685
686 fn done(self: &mut AcquireProgress, item: &ItemDesc);
688
689 fn start(self: &mut AcquireProgress);
691
692 fn stop(self: &mut AcquireProgress);
694 }
695
696 extern "C++" {
697 type ItemDesc = crate::acquire::raw::ItemDesc;
698 type PkgAcquire = crate::acquire::raw::PkgAcquire;
699 include!("rust-apt/apt-pkg-c/types.h");
700 }
701}