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}