xacli_components/components/progress/
mod.rs1use std::{
2 io::Write,
3 sync::mpsc::{self, Receiver, RecvTimeoutError, Sender},
4 time::Duration,
5};
6
7use crossterm::{
8 cursor, queue,
9 style::Print,
10 terminal::{Clear, ClearType},
11};
12use xacli_core::Context;
13
14use crate::Result;
15
16pub struct ProgressBar {
17 total: u64,
18 message: String,
19 rx: Receiver<ProgressEvent>,
20}
21
22pub struct ProgressHandle {
23 tx: Sender<ProgressEvent>,
24}
25
26pub enum ProgressEvent {
27 Increment(u64),
28 SetProgress(u64),
29 SetMessage(String),
30 Finish,
31}
32
33impl ProgressBar {
34 pub fn new(total: u64, message: impl Into<String>) -> (Self, ProgressHandle) {
35 let (tx, rx) = mpsc::channel();
36 let bar = Self {
37 total,
38 message: message.into(),
39 rx,
40 };
41 let handle = ProgressHandle { tx };
42 (bar, handle)
43 }
44
45 pub fn run(mut self, ctx: &mut dyn Context) -> Result<()> {
46 let mut current = 0u64;
47
48 let stdout = &mut ctx.stdout();
49
50 self.render(stdout, current)?;
51
52 loop {
53 match self.rx.recv_timeout(Duration::from_millis(100)) {
54 Ok(ProgressEvent::Increment(n)) => {
55 current = (current + n).min(self.total);
56 self.render(stdout, current)?;
57 }
58 Ok(ProgressEvent::SetProgress(n)) => {
59 current = n.min(self.total);
60 self.render(stdout, current)?;
61 }
62 Ok(ProgressEvent::SetMessage(msg)) => {
63 self.message = msg;
64 self.render(stdout, current)?;
65 }
66 Ok(ProgressEvent::Finish) => {
67 current = self.total;
68 self.render(stdout, current)?;
69 println!();
70 break;
71 }
72 Err(RecvTimeoutError::Timeout) => {
73 self.render(stdout, current)?;
74 }
75 Err(RecvTimeoutError::Disconnected) => {
76 println!();
77 break;
78 }
79 }
80 }
81
82 Ok(())
83 }
84
85 fn render(&self, stdout: &mut impl Write, current: u64) -> Result<()> {
86 let percent = if self.total > 0 {
87 (current as f64 / self.total as f64) * 100.0
88 } else {
89 0.0
90 };
91
92 let bar_width = 40;
93 let filled = if self.total > 0 {
94 ((current as f64 / self.total as f64) * bar_width as f64) as usize
95 } else {
96 0
97 };
98 let bar = format!("[{}{}]", "=".repeat(filled), " ".repeat(bar_width - filled));
99
100 queue!(
101 stdout,
102 cursor::MoveToColumn(0),
103 Clear(ClearType::CurrentLine),
104 Print(&self.message),
105 Print(" "),
106 Print(&bar),
107 Print(format!(" {}/{} ({:.1}%)", current, self.total, percent)),
108 )?;
109
110 stdout.flush()?;
111 Ok(())
112 }
113}
114
115impl ProgressHandle {
116 pub fn inc(&self, n: u64) {
117 let _ = self.tx.send(ProgressEvent::Increment(n));
118 }
119
120 pub fn set(&self, n: u64) {
121 let _ = self.tx.send(ProgressEvent::SetProgress(n));
122 }
123
124 pub fn set_message(&self, msg: impl Into<String>) {
125 let _ = self.tx.send(ProgressEvent::SetMessage(msg.into()));
126 }
127
128 pub fn finish(&self) {
129 let _ = self.tx.send(ProgressEvent::Finish);
130 }
131}