1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
use crate::{fmt_log, CommandGlobalOpts, Terminal, TerminalStream};
use console::Term;
use indicatif::ProgressBar;
use ockam_api::Notification;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::broadcast::Receiver;
use tokio::sync::Mutex;
use tracing::info;
const REPORTING_CHANNEL_POLL_DELAY: Duration = Duration::from_millis(100);
const REPORTING_CHANNEL_MESSAGE_DISPLAY_DELAY: Duration = Duration::from_millis(1_000);
/// This struct displays notifications coming from the CliState when commands are executed
#[derive(Debug)]
pub struct ProgressDisplay {
/// Receive notifications from the CliState
notifications: Receiver<Notification>,
/// List of all received notifications
received: Vec<Notification>,
/// If there is a progress bar, it is used to display messages as they arrive with a spinner
/// and all the notifications are also displayed at the end with the terminal
progress_bar: Option<ProgressBar>,
/// User terminal
terminal: Terminal<TerminalStream<Term>>,
}
impl ProgressDisplay {
/// Create a new NotificationsProgress without progress bar.
/// The notifications are printed as they arrive and stay on screen
pub fn new(opts: &CommandGlobalOpts) -> ProgressDisplay {
ProgressDisplay {
notifications: opts.state.subscribe(),
received: vec![],
terminal: opts.terminal.clone(),
progress_bar: None,
}
}
/// Create a new NotificationsProgress with a progress bar.
/// The notifications are displayed by the progress bar, one after the other,
/// and can be finally displayed all at once by using the `finalize` method
#[allow(unused)]
pub fn new_with_progress_bar(opts: &CommandGlobalOpts) -> ProgressDisplay {
ProgressDisplay {
notifications: opts.state.subscribe(),
received: vec![],
terminal: opts.terminal.clone(),
progress_bar: opts.terminal.progress_spinner(),
}
}
}
impl ProgressDisplay {
/// Start displaying the progress of a given action.
/// When that action changes the values of the can_stop mutex, then the display stops.
pub async fn start(&mut self, can_stop: Arc<Mutex<bool>>) -> miette::Result<()> {
loop {
tokio::select! {
_ = tokio::time::sleep(REPORTING_CHANNEL_POLL_DELAY) => {
if *can_stop.lock().await {
self.stop_progress_bar();
break;
}
}
notification = self.notifications.recv() => {
match notification {
Ok(notification) => {
// If the progress bar is available, display the message and save it
// for later display
match self.progress_bar.as_ref() {
Some(progress_bar) => {
self.received.push(notification.clone());
// Fabricate a delay for a better UX, so the user has a chance to read the message.
progress_bar.set_message(notification);
let _ = tokio::time::sleep(REPORTING_CHANNEL_MESSAGE_DISPLAY_DELAY)
.await;
},
None => {
let _ = self.terminal.write_line(fmt_log!("{}", notification));
}
};
}
// Unknown problem with channel.
_ => {
self.stop_progress_bar();
break;
}
}
}
}
}
Ok(())
}
/// Finalize the notifications by displaying/logging them
pub fn finalize(&self) {
// If a progress bar was, then display the received notifications to user as a summary of
// what was done. If there was no progress bar, then all the notifications have already been
// printed out on the terminal
if self.progress_bar.is_some() {
self.received.iter().for_each(|msg| {
let _ = self.terminal.write_line(fmt_log!("{}", msg));
});
}
// Additionally log all the notifications for later debugging if necessary
self.received.iter().for_each(|msg| {
info!(msg);
});
}
/// Stop the progress bar if any
fn stop_progress_bar(&self) {
if let Some(progress_bar) = self.progress_bar.as_ref() {
progress_bar.finish_and_clear()
}
}
}