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);
106 status
107 }
108 }
109
110 pub(crate) fn pulse_interval(&mut self) -> usize { self.inner.pulse_interval() }
112
113 pub(crate) fn hit(&mut self, item: &ItemDesc) { self.inner.hit(item) }
115
116 pub(crate) fn fetch(&mut self, item: &ItemDesc) { self.inner.fetch(item) }
118
119 pub(crate) fn fail(&mut self, item: &ItemDesc) { self.inner.fail(item) }
121
122 pub(crate) fn pulse(&mut self, owner: &PkgAcquire) { self.inner.pulse(&self.status, owner) }
124
125 pub(crate) fn start(&mut self) { self.inner.start() }
127
128 pub(crate) fn done(&mut self, item: &ItemDesc) { self.inner.done(item) }
130
131 pub(crate) fn stop(&mut self) { self.inner.stop(&self.status) }
133}
134
135impl Default for AcquireProgress<'_> {
136 fn default() -> Self { Self::apt() }
137}
138
139unsafe impl ExternType for AcquireProgress<'_> {
141 type Id = cxx::type_id!("AcquireProgress");
142 type Kind = cxx::kind::Trivial;
143}
144
145pub struct OperationProgress<'a> {
150 inner: Box<dyn DynOperationProgress + 'a>,
151}
152
153impl<'a> OperationProgress<'a> {
154 pub fn new(inner: impl DynOperationProgress + 'static) -> Self {
157 Self {
158 inner: Box::new(inner),
159 }
160 }
161
162 pub fn quiet() -> Self { Self::new(NoOpProgress {}) }
166
167 fn update(&mut self, operation: String, percent: f32) { self.inner.update(operation, percent) }
169
170 fn done(&mut self) { self.inner.done() }
172
173 pub fn pin(&mut self) -> Pin<&mut OperationProgress<'a>> { Pin::new(self) }
174}
175
176impl Default for OperationProgress<'_> {
177 fn default() -> Self { Self::quiet() }
178}
179
180unsafe impl ExternType for OperationProgress<'_> {
182 type Id = cxx::type_id!("OperationProgress");
183 type Kind = cxx::kind::Trivial;
184}
185
186pub enum InstallProgress<'a> {
190 Fancy(InstallProgressFancy<'a>),
191 Fd(RawFd),
192}
193
194impl InstallProgress<'_> {
195 pub fn new(inner: impl DynInstallProgress + 'static) -> Self {
198 Self::Fancy(InstallProgressFancy::new(inner))
199 }
200
201 pub fn fd(fd: RawFd) -> Self { Self::Fd(fd) }
204
205 pub fn apt() -> Self { Self::new(AptInstallProgress::new()) }
207}
208
209impl Default for InstallProgress<'_> {
210 fn default() -> Self { Self::apt() }
211}
212
213pub struct InstallProgressFancy<'a> {
217 inner: Box<dyn DynInstallProgress + 'a>,
218}
219
220impl<'a> InstallProgressFancy<'a> {
221 pub fn new(inner: impl DynInstallProgress + 'static) -> Self {
224 Self {
225 inner: Box::new(inner),
226 }
227 }
228
229 pub fn apt() -> Self { Self::new(AptInstallProgress::new()) }
231
232 fn status_changed(
233 &mut self,
234 pkgname: String,
235 steps_done: u64,
236 total_steps: u64,
237 action: String,
238 ) {
239 self.inner
240 .status_changed(pkgname, steps_done, total_steps, action)
241 }
242
243 fn error(&mut self, pkgname: String, steps_done: u64, total_steps: u64, error: String) {
244 self.inner.error(pkgname, steps_done, total_steps, error)
245 }
246
247 pub fn pin(&mut self) -> Pin<&mut InstallProgressFancy<'a>> { Pin::new(self) }
248}
249
250impl Default for InstallProgressFancy<'_> {
251 fn default() -> Self { Self::apt() }
252}
253
254unsafe impl ExternType for InstallProgressFancy<'_> {
256 type Id = cxx::type_id!("InstallProgressFancy");
257 type Kind = cxx::kind::Trivial;
258}
259
260struct NoOpProgress {}
266
267impl DynOperationProgress for NoOpProgress {
268 fn update(&mut self, _operation: String, _percent: f32) {}
269
270 fn done(&mut self) {}
271}
272
273#[derive(Default, Debug)]
277pub struct AptAcquireProgress {
278 lastline: usize,
279 pulse_interval: usize,
280 disable: bool,
281 config: Config,
282}
283
284impl AptAcquireProgress {
285 pub fn new() -> Self { Self::default() }
287
288 pub fn disable() -> Self {
290 AptAcquireProgress {
291 disable: true,
292 ..Default::default()
293 }
294 }
295
296 fn clear_last_line(&mut self, term_width: usize) {
298 if self.disable {
299 return;
300 }
301
302 if self.lastline == 0 {
303 return;
304 }
305
306 if self.lastline > term_width {
307 self.lastline = term_width
308 }
309
310 print!("\r{}", " ".repeat(self.lastline));
311 print!("\r");
312 stdout().flush().unwrap();
313 }
314}
315
316impl DynAcquireProgress for AptAcquireProgress {
317 fn pulse_interval(&self) -> usize { self.pulse_interval }
329
330 fn hit(&mut self, item: &ItemDesc) {
334 if self.disable {
335 return;
336 }
337
338 self.clear_last_line(terminal_width() - 1);
339
340 println!("\rHit:{} {}", item.owner().id(), item.description());
341 }
342
343 fn fetch(&mut self, item: &ItemDesc) {
347 if self.disable {
348 return;
349 }
350
351 self.clear_last_line(terminal_width() - 1);
352
353 let mut string = format!("\rGet:{} {}", item.owner().id(), item.description());
354
355 let file_size = item.owner().file_size();
356 if file_size != 0 {
357 string.push_str(&format!(" [{}]", unit_str(file_size, NumSys::Decimal)));
358 }
359
360 println!("{string}");
361 }
362
363 fn done(&mut self, _item: &ItemDesc) {
367 }
371
372 fn start(&mut self) { self.lastline = 0; }
379
380 fn stop(&mut self, owner: &AcqTextStatus) {
386 if self.disable {
387 return;
388 }
389
390 self.clear_last_line(terminal_width() - 1);
391
392 if pending_error() {
393 return;
394 }
395
396 if owner.fetched_bytes() != 0 {
397 println!(
398 "Fetched {} in {} ({}/s)",
399 unit_str(owner.fetched_bytes(), NumSys::Decimal),
400 time_str(owner.elapsed_time()),
401 unit_str(owner.current_cps(), NumSys::Decimal)
402 );
403 } else {
404 println!("Nothing to fetch.");
405 }
406 }
407
408 fn fail(&mut self, item: &ItemDesc) {
412 if self.disable {
413 return;
414 }
415
416 self.clear_last_line(terminal_width() - 1);
417
418 let mut show_error = true;
419 let error_text = item.owner().error_text();
420 let desc = format!("{} {}", item.owner().id(), item.description());
421
422 match item.owner().status() {
423 ItemState::StatIdle | ItemState::StatDone => {
424 println!("\rIgn: {desc}");
425 let key = "Acquire::Progress::Ignore::ShowErrorText";
426 if error_text.is_empty() || self.config.bool(key, false) {
427 show_error = false;
428 }
429 },
430 _ => {
431 println!("\rErr: {desc}");
432 },
433 }
434
435 if show_error {
436 println!("\r{error_text}");
437 }
438 }
439
440 fn pulse(&mut self, status: &AcqTextStatus, owner: &PkgAcquire) {
446 if self.disable {
447 return;
448 }
449
450 let term_width = terminal_width() - 1;
452
453 let mut string = String::new();
454 let mut percent_str = format!("\r{:.0}%", status.percent());
455 let mut eta_str = String::new();
456
457 let current_cps = status.current_cps();
459 if current_cps != 0 {
460 let _ = write!(
461 eta_str,
462 " {} {}",
463 unit_str(current_cps, NumSys::Decimal),
465 time_str((status.total_bytes() - status.current_bytes()) / current_cps)
467 );
468 }
469
470 for worker in owner.workers().iter() {
471 let mut work_string = String::new();
472 work_string.push_str(" [");
473
474 let Ok(item) = worker.item() else {
475 if !worker.status().is_empty() {
476 work_string.push_str(&worker.status());
477 work_string.push(']');
478 }
479 continue;
480 };
481
482 let id = item.owner().id();
483 if id != 0 {
484 let _ = write!(work_string, " {id} ");
485 }
486 work_string.push_str(&item.short_desc());
487
488 let sub = item.owner().active_subprocess();
489 if !sub.is_empty() {
490 work_string.push(' ');
491 work_string.push_str(&sub);
492 }
493
494 work_string.push(' ');
495 work_string.push_str(&unit_str(worker.current_size(), NumSys::Decimal));
496
497 if worker.total_size() > 0 && !item.owner().complete() {
498 let _ = write!(
499 work_string,
500 "/{} {}%",
501 unit_str(worker.total_size(), NumSys::Decimal),
502 (worker.current_size() * 100) / worker.total_size()
503 );
504 }
505
506 work_string.push(']');
507
508 if (string.len() + work_string.len() + percent_str.len() + eta_str.len()) > term_width {
509 break;
510 }
511
512 string.push_str(&work_string);
513 }
514
515 if string.is_empty() {
517 string = " [Working]".to_string()
518 }
519
520 percent_str.push_str(&string);
522
523 if !eta_str.is_empty() {
525 let fill_size = percent_str.len() + eta_str.len();
526 if fill_size < term_width {
527 percent_str.push_str(&" ".repeat(term_width - fill_size))
528 }
529 }
530
531 percent_str.push_str(&eta_str);
533
534 print!("{percent_str}");
536 stdout().flush().unwrap();
537
538 if self.lastline > percent_str.len() {
539 self.clear_last_line(term_width);
540 }
541
542 self.lastline = percent_str.len();
543 }
544}
545
546pub struct AptInstallProgress {
548 config: Config,
549}
550
551impl AptInstallProgress {
552 pub fn new() -> Self {
553 Self {
554 config: Config::new(),
555 }
556 }
557}
558
559impl Default for AptInstallProgress {
560 fn default() -> Self { Self::new() }
561}
562
563impl DynInstallProgress for AptInstallProgress {
564 fn status_changed(
565 &mut self,
566 _pkgname: String,
567 steps_done: u64,
568 total_steps: u64,
569 _action: String,
570 ) {
571 let term_height = terminal_height();
573 let term_width = terminal_width();
574
575 print!("\x1b7");
577
578 print!("\x1b[{term_height};0f");
580 std::io::stdout().flush().unwrap();
581
582 let percent = steps_done as f32 / total_steps as f32;
584 let mut percent_str = (percent * 100.0).round().to_string();
585
586 let percent_padding = match percent_str.len() {
587 1 => " ",
588 2 => " ",
589 3 => "",
590 _ => unreachable!(),
591 };
592
593 percent_str = percent_padding.to_owned() + &percent_str;
594
595 let bg_color = self
599 .config
600 .find("Dpkg::Progress-Fancy::Progress-fg", "\x1b[42m");
601 let fg_color = self
602 .config
603 .find("Dpkg::Progress-Fancy::Progress-bg", "\x1b[30m");
604 const BG_COLOR_RESET: &str = "\x1b[49m";
605 const FG_COLOR_RESET: &str = "\x1b[39m";
606
607 print!("{bg_color}{fg_color}Progress: [{percent_str}%]{BG_COLOR_RESET}{FG_COLOR_RESET} ");
608
609 const PROGRESS_STR_LEN: usize = 17;
611
612 print!(
617 "{}",
618 get_apt_progress_string(percent, (term_width - PROGRESS_STR_LEN).try_into().unwrap())
619 );
620 std::io::stdout().flush().unwrap();
621
622 print!("\x1b8");
629 std::io::stdout().flush().unwrap();
630 }
631
632 fn error(&mut self, _pkgname: String, _steps_done: u64, _total_steps: u64, _error: String) {}
634}
635
636#[allow(clippy::needless_lifetimes)]
637#[cxx::bridge]
638pub(crate) mod raw {
639 extern "Rust" {
640 type AcquireProgress<'a>;
641 type OperationProgress<'a>;
642 type InstallProgressFancy<'a>;
643
644 fn update(self: &mut OperationProgress, operation: String, percent: f32);
646
647 fn done(self: &mut OperationProgress);
649
650 fn status_changed(
652 self: &mut InstallProgressFancy,
653 pkgname: String,
654 steps_done: u64,
655 total_steps: u64,
656 action: String,
657 );
658
659 fn error(
663 self: &mut InstallProgressFancy,
664 pkgname: String,
665 steps_done: u64,
666 total_steps: u64,
667 error: String,
668 );
669
670 fn pulse_interval(self: &mut AcquireProgress) -> usize;
672
673 fn hit(self: &mut AcquireProgress, item: &ItemDesc);
675
676 fn fetch(self: &mut AcquireProgress, item: &ItemDesc);
678
679 fn fail(self: &mut AcquireProgress, item: &ItemDesc);
681
682 fn pulse(self: &mut AcquireProgress, owner: &PkgAcquire);
684
685 fn done(self: &mut AcquireProgress, item: &ItemDesc);
687
688 fn start(self: &mut AcquireProgress);
690
691 fn stop(self: &mut AcquireProgress);
693 }
694
695 extern "C++" {
696 type ItemDesc = crate::acquire::raw::ItemDesc;
697 type PkgAcquire = crate::acquire::raw::PkgAcquire;
698 include!("rust-apt/apt-pkg-c/types.h");
699 }
700}