xacli_components/components/progress/
mod.rs

1use 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}