Skip to main content

pbr/
multi.rs

1use crate::tty::move_cursor_up;
2use crate::ProgressBar;
3use crossbeam_channel::{unbounded, Receiver, Sender};
4use std::io::{Result, Stdout, Write};
5use std::str::from_utf8;
6use std::sync::atomic::{AtomicUsize, Ordering};
7use std::sync::Mutex;
8
9pub struct MultiBar<T: Write> {
10    state: Mutex<State<T>>,
11    chan: (Sender<WriteMsg>, Receiver<WriteMsg>),
12    nbars: AtomicUsize,
13}
14
15struct State<T: Write> {
16    lines: Vec<String>,
17    nlines: usize,
18    handle: T,
19}
20
21impl MultiBar<Stdout> {
22    /// Create a new MultiBar with stdout as a writer.
23    ///
24    /// # Examples
25    ///
26    /// ```no_run
27    /// use std::thread;
28    /// use pbr::MultiBar;
29    /// use std::time::Duration;
30    ///
31    /// let mut mb = MultiBar::new();
32    /// mb.println("Application header:");
33    ///
34    /// # let count = 250;
35    /// let mut p1 = mb.create_bar(count);
36    /// let _ = thread::spawn(move || {
37    ///     for _ in 0..count {
38    ///         p1.inc();
39    ///         thread::sleep(Duration::from_millis(100));
40    ///     }
41    ///     // notify the multibar that this bar finished.
42    ///     p1.finish();
43    /// });
44    ///
45    /// mb.println("add a separator between the two bars");
46    ///
47    /// let mut p2 = mb.create_bar(count * 2);
48    /// let _ = thread::spawn(move || {
49    ///     for _ in 0..count * 2 {
50    ///         p2.inc();
51    ///         thread::sleep(Duration::from_millis(100));
52    ///     }
53    ///     // notify the multibar that this bar finished.
54    ///     p2.finish();
55    /// });
56    ///
57    /// // start listen to all bars changes.
58    /// // this is a blocking operation, until all bars will finish.
59    /// // to ignore blocking, you can run it in a different thread.
60    /// mb.listen();
61    /// ```
62    pub fn new() -> MultiBar<Stdout> {
63        MultiBar::on(::std::io::stdout())
64    }
65}
66
67impl<T: Write> MultiBar<T> {
68    /// Create a new MultiBar with an arbitrary writer.
69    ///
70    /// # Examples
71    ///
72    /// ```no_run
73    /// use pbr::MultiBar;
74    /// use std::io::stderr;
75    ///
76    /// let mut mb = MultiBar::on(stderr());
77    /// // ...
78    /// // see full example in `MultiBar::new`
79    /// // ...
80    /// ```
81    pub fn on(handle: T) -> MultiBar<T> {
82        MultiBar {
83            state: Mutex::new(State {
84                lines: Vec::new(),
85                handle,
86                nlines: 0,
87            }),
88            chan: unbounded(),
89            nbars: AtomicUsize::new(0),
90        }
91    }
92
93    /// println used to add text lines between the bars.
94    /// for example: you could add a header to your application,
95    /// or text separators between bars.
96    ///
97    /// # Examples
98    ///
99    /// ```no_run
100    /// use pbr::MultiBar;
101    ///
102    /// let mut mb = MultiBar::new();
103    /// mb.println("Application header:");
104    ///
105    /// # let count = 250;
106    /// let mut p1 = mb.create_bar(count);
107    /// // ...
108    ///
109    /// mb.println("Text line between bar1 and bar2");
110    ///
111    /// let mut p2 = mb.create_bar(count);
112    /// // ...
113    ///
114    /// mb.println("Text line between bar2 and bar3");
115    ///
116    /// // ...
117    /// // ...
118    /// mb.listen();
119    /// ```
120    pub fn println(&self, s: &str) {
121        let mut state = self.state.lock().unwrap();
122        state.lines.push(s.to_owned());
123        state.nlines += 1;
124    }
125
126    /// create_bar creates new `ProgressBar` with `Pipe` as the writer.
127    ///
128    /// The ordering of the method calls is important. it means that in
129    /// the first call, you get a progress bar in level 1, in the 2nd call,
130    /// you get a progress bar in level 2, and so on.
131    ///
132    /// ProgressBar that finish its work, must call `finish()` (or `finish_print`)
133    /// to notify the `MultiBar` about it.
134    ///
135    /// # Examples
136    ///
137    /// ```no_run
138    /// use pbr::MultiBar;
139    ///
140    /// let mut mb = MultiBar::new();
141    /// # let (count1, count2, count3) = (250, 62500, 15625000);
142    ///
143    /// // progress bar in level 1
144    /// let mut p1 = mb.create_bar(count1);
145    /// // ...
146    ///
147    /// // progress bar in level 2
148    /// let mut p2 = mb.create_bar(count2);
149    /// // ...
150    ///
151    /// // progress bar in level 3
152    /// let mut p3 = mb.create_bar(count3);
153    ///
154    /// // ...
155    /// mb.listen();
156    /// ```
157    pub fn create_bar(&self, total: u64) -> ProgressBar<Pipe> {
158        let mut state = self.state.lock().unwrap();
159
160        state.lines.push(String::new());
161        state.nlines += 1;
162
163        self.nbars.fetch_add(1, Ordering::SeqCst);
164
165        let mut p = ProgressBar::on(
166            Pipe {
167                level: state.nlines - 1,
168                chan: self.chan.0.clone(),
169            },
170            total,
171        );
172
173        p.is_multibar = true;
174        p.add(0);
175        p
176    }
177
178    /// listen start listen to all bars changes.
179    ///
180    /// `ProgressBar` that finish its work, must call `finish()` (or `finish_print`)
181    /// to notify the `MultiBar` about it.
182    ///
183    /// This is a blocking operation and blocks until all bars will
184    /// finish.
185    /// To ignore blocking, you can run it in a different thread.
186    ///
187    /// # Examples
188    ///
189    /// ```no_run
190    /// use std::thread;
191    /// use pbr::MultiBar;
192    ///
193    /// let mut mb = MultiBar::new();
194    ///
195    /// // ...
196    /// // create some bars here
197    /// // ...
198    ///
199    /// thread::spawn(move || {
200    ///     mb.listen();
201    ///     println!("all bars done!");
202    /// });
203    ///
204    /// // ...
205    /// ```
206    pub fn listen(&self) {
207        let mut first = true;
208        let mut out = String::new();
209
210        while self.nbars.load(Ordering::SeqCst) > 0 {
211            // receive message
212            let msg = self.chan.1.recv().unwrap();
213            if msg.done {
214                self.nbars.fetch_sub(1, Ordering::SeqCst);
215                continue;
216            }
217
218            out.clear();
219            let mut state = self.state.lock().unwrap();
220            state.lines[msg.level] = msg.string;
221
222            // and draw
223            if !first {
224                out += &move_cursor_up(state.nlines);
225            } else {
226                first = false;
227            }
228
229            for l in state.lines.iter() {
230                out.push_str(&format!("\r{}\n", l));
231            }
232
233            printfl!(state.handle, "{}", out);
234        }
235    }
236}
237
238pub struct Pipe {
239    level: usize,
240    chan: Sender<WriteMsg>,
241}
242
243impl Write for Pipe {
244    fn write(&mut self, buf: &[u8]) -> Result<usize> {
245        let s = from_utf8(buf).unwrap().to_owned();
246        self.chan
247            .send(WriteMsg {
248                // finish method emit empty string
249                done: s.is_empty(),
250                level: self.level,
251                string: s,
252            })
253            .unwrap();
254        Ok(buf.len())
255    }
256
257    fn flush(&mut self) -> Result<()> {
258        Ok(())
259    }
260}
261
262// WriteMsg is the message format used to communicate
263// between MultiBar and its bars
264struct WriteMsg {
265    done: bool,
266    level: usize,
267    string: String,
268}