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 {
86 Self::new(AptAcquireProgress::new())
87 }
88
89 pub fn quiet() -> Self {
91 Self::new(AptAcquireProgress::disable())
92 }
93
94 pub fn mut_status(&mut self) -> Pin<&mut AcqTextStatus> {
97 unsafe {
98 let raw_ptr = &mut *(self as *mut AcquireProgress);
100 let mut status = self.status.pin_mut();
103
104 status.as_mut().set_callback(raw_ptr);
110 status
111 }
112 }
113
114 pub(crate) fn pulse_interval(&mut self) -> usize {
116 self.inner.pulse_interval()
117 }
118
119 pub(crate) fn hit(&mut self, item: &ItemDesc) {
121 self.inner.hit(item)
122 }
123
124 pub(crate) fn fetch(&mut self, item: &ItemDesc) {
126 self.inner.fetch(item)
127 }
128
129 pub(crate) fn fail(&mut self, item: &ItemDesc) {
131 self.inner.fail(item)
132 }
133
134 pub(crate) fn pulse(&mut self, owner: &PkgAcquire) {
136 self.inner.pulse(&self.status, owner)
137 }
138
139 pub(crate) fn start(&mut self) {
141 self.inner.start()
142 }
143
144 pub(crate) fn done(&mut self, item: &ItemDesc) {
146 self.inner.done(item)
147 }
148
149 pub(crate) fn stop(&mut self) {
151 self.inner.stop(&self.status)
152 }
153}
154
155impl Default for AcquireProgress<'_> {
156 fn default() -> Self {
157 Self::apt()
158 }
159}
160
161unsafe impl ExternType for AcquireProgress<'_> {
163 type Id = cxx::type_id!("AcquireProgress");
164 type Kind = cxx::kind::Trivial;
165}
166
167pub struct OperationProgress<'a> {
172 inner: Box<dyn DynOperationProgress + 'a>,
173}
174
175impl<'a> OperationProgress<'a> {
176 pub fn new(inner: impl DynOperationProgress + 'static) -> Self {
179 Self {
180 inner: Box::new(inner),
181 }
182 }
183
184 pub fn quiet() -> Self {
188 Self::new(NoOpProgress {})
189 }
190
191 fn update(&mut self, operation: String, percent: f32) {
193 self.inner.update(operation, percent)
194 }
195
196 fn done(&mut self) {
198 self.inner.done()
199 }
200
201 pub fn pin(&mut self) -> Pin<&mut OperationProgress<'a>> {
202 Pin::new(self)
203 }
204}
205
206impl Default for OperationProgress<'_> {
207 fn default() -> Self {
208 Self::quiet()
209 }
210}
211
212unsafe impl ExternType for OperationProgress<'_> {
214 type Id = cxx::type_id!("OperationProgress");
215 type Kind = cxx::kind::Trivial;
216}
217
218pub enum InstallProgress<'a> {
222 Fancy(InstallProgressFancy<'a>),
223 Fd(RawFd),
224}
225
226impl InstallProgress<'_> {
227 pub fn new(inner: impl DynInstallProgress + 'static) -> Self {
230 Self::Fancy(InstallProgressFancy::new(inner))
231 }
232
233 pub fn fd(fd: RawFd) -> Self {
236 Self::Fd(fd)
237 }
238
239 pub fn apt() -> Self {
241 Self::new(AptInstallProgress::new())
242 }
243}
244
245impl Default for InstallProgress<'_> {
246 fn default() -> Self {
247 Self::apt()
248 }
249}
250
251pub struct InstallProgressFancy<'a> {
255 inner: Box<dyn DynInstallProgress + 'a>,
256}
257
258impl<'a> InstallProgressFancy<'a> {
259 pub fn new(inner: impl DynInstallProgress + 'static) -> Self {
262 Self {
263 inner: Box::new(inner),
264 }
265 }
266
267 pub fn apt() -> Self {
269 Self::new(AptInstallProgress::new())
270 }
271
272 fn status_changed(
273 &mut self,
274 pkgname: String,
275 steps_done: u64,
276 total_steps: u64,
277 action: String,
278 ) {
279 self.inner
280 .status_changed(pkgname, steps_done, total_steps, action)
281 }
282
283 fn error(&mut self, pkgname: String, steps_done: u64, total_steps: u64, error: String) {
284 self.inner.error(pkgname, steps_done, total_steps, error)
285 }
286
287 pub fn pin(&mut self) -> Pin<&mut InstallProgressFancy<'a>> {
288 Pin::new(self)
289 }
290}
291
292impl Default for InstallProgressFancy<'_> {
293 fn default() -> Self {
294 Self::apt()
295 }
296}
297
298unsafe impl ExternType for InstallProgressFancy<'_> {
300 type Id = cxx::type_id!("InstallProgressFancy");
301 type Kind = cxx::kind::Trivial;
302}
303
304struct NoOpProgress {}
310
311impl DynOperationProgress for NoOpProgress {
312 fn update(&mut self, _operation: String, _percent: f32) {}
313
314 fn done(&mut self) {}
315}
316
317#[derive(Default, Debug)]
321pub struct AptAcquireProgress {
322 lastline: usize,
323 pulse_interval: usize,
324 disable: bool,
325 config: Config,
326}
327
328impl AptAcquireProgress {
329 pub fn new() -> Self {
331 Self::default()
332 }
333
334 pub fn disable() -> Self {
336 AptAcquireProgress {
337 disable: true,
338 ..Default::default()
339 }
340 }
341
342 fn clear_last_line(&mut self, term_width: usize) {
344 if self.disable {
345 return;
346 }
347
348 if self.lastline == 0 {
349 return;
350 }
351
352 if self.lastline > term_width {
353 self.lastline = term_width
354 }
355
356 print!("\r{}", " ".repeat(self.lastline));
357 print!("\r");
358 stdout().flush().unwrap();
359 }
360}
361
362impl DynAcquireProgress for AptAcquireProgress {
363 fn pulse_interval(&self) -> usize {
375 self.pulse_interval
376 }
377
378 fn hit(&mut self, item: &ItemDesc) {
382 if self.disable {
383 return;
384 }
385
386 self.clear_last_line(terminal_width() - 1);
387
388 println!("\rHit:{} {}", item.owner().id(), item.description());
389 }
390
391 fn fetch(&mut self, item: &ItemDesc) {
395 if self.disable {
396 return;
397 }
398
399 self.clear_last_line(terminal_width() - 1);
400
401 let mut string = format!("\rGet:{} {}", item.owner().id(), item.description());
402
403 let file_size = item.owner().file_size();
404 if file_size != 0 {
405 string.push_str(&format!(" [{}]", unit_str(file_size, NumSys::Decimal)));
406 }
407
408 println!("{string}");
409 }
410
411 fn done(&mut self, _item: &ItemDesc) {
415 }
419
420 fn start(&mut self) {
427 self.lastline = 0;
428 }
429
430 fn stop(&mut self, owner: &AcqTextStatus) {
436 if self.disable {
437 return;
438 }
439
440 self.clear_last_line(terminal_width() - 1);
441
442 if pending_error() {
443 return;
444 }
445
446 if owner.fetched_bytes() != 0 {
447 println!(
448 "Fetched {} in {} ({}/s)",
449 unit_str(owner.fetched_bytes(), NumSys::Decimal),
450 time_str(owner.elapsed_time()),
451 unit_str(owner.current_cps(), NumSys::Decimal)
452 );
453 } else {
454 println!("Nothing to fetch.");
455 }
456 }
457
458 fn fail(&mut self, item: &ItemDesc) {
462 if self.disable {
463 return;
464 }
465
466 self.clear_last_line(terminal_width() - 1);
467
468 let mut show_error = true;
469 let error_text = item.owner().error_text();
470 let desc = format!("{} {}", item.owner().id(), item.description());
471
472 match item.owner().status() {
473 ItemState::StatIdle | ItemState::StatDone => {
474 println!("\rIgn: {desc}");
475 let key = "Acquire::Progress::Ignore::ShowErrorText";
476 if error_text.is_empty() || self.config.bool(key, false) {
477 show_error = false;
478 }
479 }
480 _ => {
481 println!("\rErr: {desc}");
482 }
483 }
484
485 if show_error {
486 println!("\r{error_text}");
487 }
488 }
489
490 fn pulse(&mut self, status: &AcqTextStatus, owner: &PkgAcquire) {
496 if self.disable {
497 return;
498 }
499
500 let term_width = terminal_width() - 1;
502
503 let mut string = String::new();
504 let mut percent_str = format!("\r{:.0}%", status.percent());
505 let mut eta_str = String::new();
506
507 let current_cps = status.current_cps();
509 if current_cps != 0 {
510 let _ = write!(
511 eta_str,
512 " {} {}",
513 unit_str(current_cps, NumSys::Decimal),
515 time_str((status.total_bytes() - status.current_bytes()) / current_cps)
517 );
518 }
519
520 for worker in owner.workers().iter() {
521 let mut work_string = String::new();
522 work_string.push_str(" [");
523
524 let Ok(item) = worker.item() else {
525 if !worker.status().is_empty() {
526 work_string.push_str(&worker.status());
527 work_string.push(']');
528 }
529 continue;
530 };
531
532 let id = item.owner().id();
533 if id != 0 {
534 let _ = write!(work_string, " {id} ");
535 }
536 work_string.push_str(&item.short_desc());
537
538 let sub = item.owner().active_subprocess();
539 if !sub.is_empty() {
540 work_string.push(' ');
541 work_string.push_str(&sub);
542 }
543
544 work_string.push(' ');
545 work_string.push_str(&unit_str(worker.current_size(), NumSys::Decimal));
546
547 if worker.total_size() > 0 && !item.owner().complete() {
548 let _ = write!(
549 work_string,
550 "/{} {}%",
551 unit_str(worker.total_size(), NumSys::Decimal),
552 (worker.current_size() * 100) / worker.total_size()
553 );
554 }
555
556 work_string.push(']');
557
558 if (string.len() + work_string.len() + percent_str.len() + eta_str.len()) > term_width {
559 break;
560 }
561
562 string.push_str(&work_string);
563 }
564
565 if string.is_empty() {
567 string = " [Working]".to_string()
568 }
569
570 percent_str.push_str(&string);
572
573 if !eta_str.is_empty() {
575 let fill_size = percent_str.len() + eta_str.len();
576 if fill_size < term_width {
577 percent_str.push_str(&" ".repeat(term_width - fill_size))
578 }
579 }
580
581 percent_str.push_str(&eta_str);
583
584 print!("{percent_str}");
586 stdout().flush().unwrap();
587
588 if self.lastline > percent_str.len() {
589 self.clear_last_line(term_width);
590 }
591
592 self.lastline = percent_str.len();
593 }
594}
595
596pub struct AptInstallProgress {
598 config: Config,
599}
600
601impl AptInstallProgress {
602 pub fn new() -> Self {
603 Self {
604 config: Config::new(),
605 }
606 }
607}
608
609impl Default for AptInstallProgress {
610 fn default() -> Self {
611 Self::new()
612 }
613}
614
615impl DynInstallProgress for AptInstallProgress {
616 fn status_changed(
617 &mut self,
618 _pkgname: String,
619 steps_done: u64,
620 total_steps: u64,
621 _action: String,
622 ) {
623 let term_height = terminal_height();
625 let term_width = terminal_width();
626
627 print!("\x1b7");
629
630 print!("\x1b[{term_height};0f");
632 std::io::stdout().flush().unwrap();
633
634 let percent = steps_done as f32 / total_steps as f32;
636 let mut percent_str = (percent * 100.0).round().to_string();
637
638 let percent_padding = match percent_str.len() {
639 1 => " ",
640 2 => " ",
641 3 => "",
642 _ => unreachable!(),
643 };
644
645 percent_str = percent_padding.to_owned() + &percent_str;
646
647 let bg_color = self
651 .config
652 .find("Dpkg::Progress-Fancy::Progress-fg", "\x1b[42m");
653 let fg_color = self
654 .config
655 .find("Dpkg::Progress-Fancy::Progress-bg", "\x1b[30m");
656 const BG_COLOR_RESET: &str = "\x1b[49m";
657 const FG_COLOR_RESET: &str = "\x1b[39m";
658
659 print!("{bg_color}{fg_color}Progress: [{percent_str}%]{BG_COLOR_RESET}{FG_COLOR_RESET} ");
660
661 const PROGRESS_STR_LEN: usize = 17;
663
664 print!(
669 "{}",
670 get_apt_progress_string(percent, (term_width - PROGRESS_STR_LEN).try_into().unwrap())
671 );
672 std::io::stdout().flush().unwrap();
673
674 print!("\x1b8");
681 std::io::stdout().flush().unwrap();
682 }
683
684 fn error(&mut self, _pkgname: String, _steps_done: u64, _total_steps: u64, _error: String) {}
686}
687
688#[allow(clippy::needless_lifetimes)]
689#[cxx::bridge]
690pub(crate) mod raw {
691 extern "Rust" {
692 type AcquireProgress<'a>;
693 type OperationProgress<'a>;
694 type InstallProgressFancy<'a>;
695
696 fn update(self: &mut OperationProgress, operation: String, percent: f32);
698
699 fn done(self: &mut OperationProgress);
701
702 fn status_changed(
704 self: &mut InstallProgressFancy,
705 pkgname: String,
706 steps_done: u64,
707 total_steps: u64,
708 action: String,
709 );
710
711 fn error(
715 self: &mut InstallProgressFancy,
716 pkgname: String,
717 steps_done: u64,
718 total_steps: u64,
719 error: String,
720 );
721
722 fn pulse_interval(self: &mut AcquireProgress) -> usize;
724
725 fn hit(self: &mut AcquireProgress, item: &ItemDesc);
727
728 fn fetch(self: &mut AcquireProgress, item: &ItemDesc);
730
731 fn fail(self: &mut AcquireProgress, item: &ItemDesc);
733
734 fn pulse(self: &mut AcquireProgress, owner: &PkgAcquire);
736
737 fn done(self: &mut AcquireProgress, item: &ItemDesc);
739
740 fn start(self: &mut AcquireProgress);
742
743 fn stop(self: &mut AcquireProgress);
745 }
746
747 extern "C++" {
748 type ItemDesc = crate::acquire::raw::ItemDesc;
749 type PkgAcquire = crate::acquire::raw::PkgAcquire;
750 include!("oma-apt/apt-pkg-c/types.h");
751 }
752}