1use std::fmt::Write as _;
3use std::io::{stdout, Write};
4use std::pin::Pin;
5
6use cxx::{ExternType, UniquePtr};
7
8use crate::config::Config;
9use crate::error::raw::pending_error;
10use crate::raw::{acquire_status, AcqTextStatus, ItemDesc, ItemState, PkgAcquire};
11use crate::util::{
12 get_apt_progress_string, terminal_height, terminal_width, time_str, unit_str, NumSys,
13};
14
15pub trait DynAcquireProgress {
17 fn pulse_interval(&self) -> usize;
19
20 fn hit(&mut self, item: &ItemDesc);
22
23 fn fetch(&mut self, item: &ItemDesc);
25
26 fn fail(&mut self, item: &ItemDesc);
28
29 fn pulse(&mut self, status: &AcqTextStatus, owner: &PkgAcquire);
31
32 fn done(&mut self, item: &ItemDesc);
34
35 fn start(&mut self);
37
38 fn stop(&mut self, status: &AcqTextStatus);
40}
41
42pub trait DynOperationProgress {
44 fn update(&mut self, operation: String, percent: f32);
45 fn done(&mut self);
46}
47
48pub trait DynInstallProgress {
50 fn status_changed(
51 &mut self,
52 pkgname: String,
53 steps_done: u64,
54 total_steps: u64,
55 action: String,
56 );
57 fn error(&mut self, pkgname: String, steps_done: u64, total_steps: u64, error: String);
58}
59
60pub struct AcquireProgress<'a> {
68 status: UniquePtr<AcqTextStatus>,
69 inner: Box<dyn DynAcquireProgress + 'a>,
70}
71
72impl<'a> AcquireProgress<'a> {
73 pub fn new(inner: impl DynAcquireProgress + 'a) -> Self {
76 Self {
77 status: unsafe { acquire_status() },
78 inner: Box::new(inner),
79 }
80 }
81
82 pub fn apt() -> Self { Self::new(AptAcquireProgress::new()) }
85
86 pub fn quiet() -> Self { Self::new(AptAcquireProgress::disable()) }
88
89 pub fn mut_status(&mut self) -> Pin<&mut AcqTextStatus> {
92 unsafe {
93 let raw_ptr = &mut *(self as *mut AcquireProgress);
95 let mut status = self.status.pin_mut();
98
99 status.as_mut().set_callback(raw_ptr);
105 status
106 }
107 }
108
109 pub(crate) fn pulse_interval(&mut self) -> usize { self.inner.pulse_interval() }
111
112 pub(crate) fn hit(&mut self, item: &ItemDesc) { self.inner.hit(item) }
114
115 pub(crate) fn fetch(&mut self, item: &ItemDesc) { self.inner.fetch(item) }
117
118 pub(crate) fn fail(&mut self, item: &ItemDesc) { self.inner.fail(item) }
120
121 pub(crate) fn pulse(&mut self, owner: &PkgAcquire) { self.inner.pulse(&self.status, owner) }
123
124 pub(crate) fn start(&mut self) { self.inner.start() }
126
127 pub(crate) fn done(&mut self, item: &ItemDesc) { self.inner.done(item) }
129
130 pub(crate) fn stop(&mut self) { self.inner.stop(&self.status) }
132}
133
134impl<'a> Default for AcquireProgress<'a> {
135 fn default() -> Self { Self::apt() }
136}
137
138unsafe impl<'a> ExternType for AcquireProgress<'a> {
140 type Id = cxx::type_id!("AcquireProgress");
141 type Kind = cxx::kind::Trivial;
142}
143
144pub struct OperationProgress<'a> {
149 inner: Box<dyn DynOperationProgress + 'a>,
150}
151
152impl<'a> OperationProgress<'a> {
153 pub fn new(inner: impl DynOperationProgress + 'static) -> Self {
156 Self {
157 inner: Box::new(inner),
158 }
159 }
160
161 pub fn quiet() -> Self { Self::new(NoOpProgress {}) }
165
166 fn update(&mut self, operation: String, percent: f32) { self.inner.update(operation, percent) }
168
169 fn done(&mut self) { self.inner.done() }
171
172 pub fn pin(&mut self) -> Pin<&mut OperationProgress<'a>> { Pin::new(self) }
173}
174
175impl<'a> Default for OperationProgress<'a> {
176 fn default() -> Self { Self::quiet() }
177}
178
179unsafe impl<'a> ExternType for OperationProgress<'a> {
181 type Id = cxx::type_id!("OperationProgress");
182 type Kind = cxx::kind::Trivial;
183}
184
185pub struct InstallProgress<'a> {
189 inner: Box<dyn DynInstallProgress + 'a>,
190}
191
192impl<'a> InstallProgress<'a> {
193 pub fn new(inner: impl DynInstallProgress + 'static) -> Self {
196 Self {
197 inner: Box::new(inner),
198 }
199 }
200
201 pub fn apt() -> Self { Self::new(AptInstallProgress::new()) }
205
206 fn status_changed(
207 &mut self,
208 pkgname: String,
209 steps_done: u64,
210 total_steps: u64,
211 action: String,
212 ) {
213 self.inner
214 .status_changed(pkgname, steps_done, total_steps, action)
215 }
216
217 fn error(&mut self, pkgname: String, steps_done: u64, total_steps: u64, error: String) {
218 self.inner.error(pkgname, steps_done, total_steps, error)
219 }
220
221 pub fn pin(&mut self) -> Pin<&mut InstallProgress<'a>> { Pin::new(self) }
222}
223
224impl<'a> Default for InstallProgress<'a> {
225 fn default() -> Self { Self::apt() }
226}
227
228unsafe impl<'a> ExternType for InstallProgress<'a> {
230 type Id = cxx::type_id!("InstallProgress");
231 type Kind = cxx::kind::Trivial;
232}
233
234struct NoOpProgress {}
240
241impl DynOperationProgress for NoOpProgress {
242 fn update(&mut self, _operation: String, _percent: f32) {}
243
244 fn done(&mut self) {}
245}
246
247#[derive(Default, Debug)]
251pub struct AptAcquireProgress {
252 lastline: usize,
253 pulse_interval: usize,
254 disable: bool,
255 config: Config,
256}
257
258impl AptAcquireProgress {
259 pub fn new() -> Self { Self::default() }
261
262 pub fn disable() -> Self {
264 AptAcquireProgress {
265 disable: true,
266 ..Default::default()
267 }
268 }
269
270 fn clear_last_line(&mut self, term_width: usize) {
272 if self.disable {
273 return;
274 }
275
276 if self.lastline == 0 {
277 return;
278 }
279
280 if self.lastline > term_width {
281 self.lastline = term_width
282 }
283
284 print!("\r{}", " ".repeat(self.lastline));
285 print!("\r");
286 stdout().flush().unwrap();
287 }
288}
289
290impl DynAcquireProgress for AptAcquireProgress {
291 fn pulse_interval(&self) -> usize { self.pulse_interval }
303
304 fn hit(&mut self, item: &ItemDesc) {
308 if self.disable {
309 return;
310 }
311
312 self.clear_last_line(terminal_width() - 1);
313
314 println!("\rHit:{} {}", item.owner().id(), item.description());
315 }
316
317 fn fetch(&mut self, item: &ItemDesc) {
321 if self.disable {
322 return;
323 }
324
325 self.clear_last_line(terminal_width() - 1);
326
327 let mut string = format!("\rGet:{} {}", item.owner().id(), item.description());
328
329 let file_size = item.owner().file_size();
330 if file_size != 0 {
331 string.push_str(&format!(" [{}]", unit_str(file_size, NumSys::Decimal)));
332 }
333
334 println!("{string}");
335 }
336
337 fn done(&mut self, _item: &ItemDesc) {
341 }
345
346 fn start(&mut self) { self.lastline = 0; }
353
354 fn stop(&mut self, owner: &AcqTextStatus) {
360 if self.disable {
361 return;
362 }
363
364 self.clear_last_line(terminal_width() - 1);
365
366 if pending_error() {
367 return;
368 }
369
370 if owner.fetched_bytes() != 0 {
371 println!(
372 "Fetched {} in {} ({}/s)",
373 unit_str(owner.fetched_bytes(), NumSys::Decimal),
374 time_str(owner.elapsed_time()),
375 unit_str(owner.current_cps(), NumSys::Decimal)
376 );
377 } else {
378 println!("Nothing to fetch.");
379 }
380 }
381
382 fn fail(&mut self, item: &ItemDesc) {
386 if self.disable {
387 return;
388 }
389
390 self.clear_last_line(terminal_width() - 1);
391
392 let mut show_error = true;
393 let error_text = item.owner().error_text();
394 let desc = format!("{} {}", item.owner().id(), item.description());
395
396 match item.owner().status() {
397 ItemState::StatIdle | ItemState::StatDone => {
398 println!("\rIgn: {desc}");
399 let key = "Acquire::Progress::Ignore::ShowErrorText";
400 if error_text.is_empty() || self.config.bool(key, false) {
401 show_error = false;
402 }
403 },
404 _ => {
405 println!("\rErr: {desc}");
406 },
407 }
408
409 if show_error {
410 println!("\r{error_text}");
411 }
412 }
413
414 fn pulse(&mut self, status: &AcqTextStatus, owner: &PkgAcquire) {
420 if self.disable {
421 return;
422 }
423
424 let term_width = terminal_width() - 1;
426
427 let mut string = String::new();
428 let mut percent_str = format!("\r{:.0}%", status.percent());
429 let mut eta_str = String::new();
430
431 let current_cps = status.current_cps();
433 if current_cps != 0 {
434 let _ = write!(
435 eta_str,
436 " {} {}",
437 unit_str(current_cps, NumSys::Decimal),
439 time_str((status.total_bytes() - status.current_bytes()) / current_cps)
441 );
442 }
443
444 for worker in owner.workers().iter() {
445 let mut work_string = String::new();
446 work_string.push_str(" [");
447
448 let Ok(item) = worker.item() else {
449 if !worker.status().is_empty() {
450 work_string.push_str(&worker.status());
451 work_string.push(']');
452 }
453 continue;
454 };
455
456 let id = item.owner().id();
457 if id != 0 {
458 let _ = write!(work_string, " {id} ");
459 }
460 work_string.push_str(&item.short_desc());
461
462 let sub = item.owner().active_subprocess();
463 if !sub.is_empty() {
464 work_string.push(' ');
465 work_string.push_str(&sub);
466 }
467
468 work_string.push(' ');
469 work_string.push_str(&unit_str(worker.current_size(), NumSys::Decimal));
470
471 if worker.total_size() > 0 && !item.owner().complete() {
472 let _ = write!(
473 work_string,
474 "/{} {}%",
475 unit_str(worker.total_size(), NumSys::Decimal),
476 (worker.current_size() * 100) / worker.total_size()
477 );
478 }
479
480 work_string.push(']');
481
482 if (string.len() + work_string.len() + percent_str.len() + eta_str.len()) > term_width {
483 break;
484 }
485
486 string.push_str(&work_string);
487 }
488
489 if string.is_empty() {
491 string = " [Working]".to_string()
492 }
493
494 percent_str.push_str(&string);
496
497 if !eta_str.is_empty() {
499 let fill_size = percent_str.len() + eta_str.len();
500 if fill_size < term_width {
501 percent_str.push_str(&" ".repeat(term_width - fill_size))
502 }
503 }
504
505 percent_str.push_str(&eta_str);
507
508 print!("{percent_str}");
510 stdout().flush().unwrap();
511
512 if self.lastline > percent_str.len() {
513 self.clear_last_line(term_width);
514 }
515
516 self.lastline = percent_str.len();
517 }
518}
519
520pub struct AptInstallProgress {
522 config: Config,
523}
524
525impl AptInstallProgress {
526 pub fn new() -> Self {
527 Self {
528 config: Config::new(),
529 }
530 }
531}
532
533impl Default for AptInstallProgress {
534 fn default() -> Self { Self::new() }
535}
536
537impl DynInstallProgress for AptInstallProgress {
538 fn status_changed(
539 &mut self,
540 _pkgname: String,
541 steps_done: u64,
542 total_steps: u64,
543 _action: String,
544 ) {
545 let term_height = terminal_height();
547 let term_width = terminal_width();
548
549 print!("\x1b7");
551
552 print!("\x1b[{term_height};0f");
554 std::io::stdout().flush().unwrap();
555
556 let percent = steps_done as f32 / total_steps as f32;
558 let mut percent_str = (percent * 100.0).round().to_string();
559
560 let percent_padding = match percent_str.len() {
561 1 => " ",
562 2 => " ",
563 3 => "",
564 _ => unreachable!(),
565 };
566
567 percent_str = percent_padding.to_owned() + &percent_str;
568
569 let bg_color = self
573 .config
574 .find("Dpkg::Progress-Fancy::Progress-fg", "\x1b[42m");
575 let fg_color = self
576 .config
577 .find("Dpkg::Progress-Fancy::Progress-bg", "\x1b[30m");
578 const BG_COLOR_RESET: &str = "\x1b[49m";
579 const FG_COLOR_RESET: &str = "\x1b[39m";
580
581 print!("{bg_color}{fg_color}Progress: [{percent_str}%]{BG_COLOR_RESET}{FG_COLOR_RESET} ");
582
583 const PROGRESS_STR_LEN: usize = 17;
585
586 print!(
591 "{}",
592 get_apt_progress_string(percent, (term_width - PROGRESS_STR_LEN).try_into().unwrap())
593 );
594 std::io::stdout().flush().unwrap();
595
596 print!("\x1b8");
603 std::io::stdout().flush().unwrap();
604 }
605
606 fn error(&mut self, _pkgname: String, _steps_done: u64, _total_steps: u64, _error: String) {}
608}
609
610#[cxx::bridge]
611pub(crate) mod raw {
612 extern "Rust" {
613 type AcquireProgress<'a>;
614 type OperationProgress<'a>;
615 type InstallProgress<'a>;
616
617 fn update(self: &mut OperationProgress, operation: String, percent: f32);
619
620 fn done(self: &mut OperationProgress);
622
623 fn status_changed(
625 self: &mut InstallProgress,
626 pkgname: String,
627 steps_done: u64,
628 total_steps: u64,
629 action: String,
630 );
631
632 fn error(
636 self: &mut InstallProgress,
637 pkgname: String,
638 steps_done: u64,
639 total_steps: u64,
640 error: String,
641 );
642
643 fn pulse_interval(self: &mut AcquireProgress) -> usize;
645
646 fn hit(self: &mut AcquireProgress, item: &ItemDesc);
648
649 fn fetch(self: &mut AcquireProgress, item: &ItemDesc);
651
652 fn fail(self: &mut AcquireProgress, item: &ItemDesc);
654
655 fn pulse(self: &mut AcquireProgress, owner: &PkgAcquire);
657
658 fn done(self: &mut AcquireProgress, item: &ItemDesc);
660
661 fn start(self: &mut AcquireProgress);
663
664 fn stop(self: &mut AcquireProgress);
666 }
667
668 extern "C++" {
669 type ItemDesc = crate::acquire::raw::ItemDesc;
670 type PkgAcquire = crate::acquire::raw::PkgAcquire;
671 include!("rust-apt/apt-pkg-c/types.h");
672 }
673}