howudoin/
tx.rs

1use super::*;
2use flume::{bounded, unbounded, Sender};
3use parking_lot::RwLock;
4use Payload::*;
5
6static TX: StaticTx = StaticTx::none();
7
8/// Initialise the progress consumer loop.
9///
10/// Progress reports are only sent through if an `init` call has previously been invoked.
11///
12/// # Example
13/// ```rust
14/// howudoin::init(howudoin::consumers::Noop::default());
15/// ```
16pub fn init<C: Consume + Send + 'static>(consumer: C) {
17    let (tx, rx) = unbounded();
18    TX.set_tx(tx);
19
20    std::thread::spawn(|| super::rx::spawn(rx, consumer));
21}
22
23/// Disable the progress reporting consumer loop, terminating the sender side.
24///
25/// This is effectively the opposite of [`init`].
26pub fn disable() {
27    TX.disable()
28}
29
30/// Generate a new progress reporter.
31///
32/// The reporter's parent will be the _last_ report generated (if one exists).
33///
34/// # Example
35/// ```rust
36/// let rpt = howudoin::new().label("Progress");
37/// ```
38pub fn new() -> Tx {
39    new_(|x| AddReport(None, x))
40}
41
42/// Generate a new progress reporter under a parent.
43///
44/// # Example
45/// ```rust
46/// let parent = howudoin::new().label("Parent");
47/// let child = howudoin::new_with_parent(parent.id());
48/// ```
49pub fn new_with_parent(parent: Id) -> Tx {
50    new_(|x| AddReport(Some(parent), x))
51}
52
53/// Generate a new progress reporter at the root level.
54///
55/// # Example
56/// ```rust
57/// let rpt = howudoin::new_root().label("Progress");
58/// ```
59pub fn new_root() -> Tx {
60    new_(AddRootReport)
61}
62
63fn new_<F: FnOnce(Sender<Id>) -> Payload>(f: F) -> Tx {
64    let (tx, rx) = bounded(1);
65    TX.send(|| f(tx));
66    let id = rx.recv_timeout(Duration::from_millis(500)).unwrap_or(0);
67
68    Tx { id }
69}
70
71/// Fetch the progress report tree.
72///
73/// The returned structure is a tree of progress reports currently tracked.
74/// If no progress has been [`init`]ialised, this will return `None`.
75///
76/// Note that [`report::Progress`] is serialisable with the `serde` feature.
77///
78/// # Example
79/// ```rust
80/// let a = howudoin::new();
81/// let b = howudoin::new();
82///
83/// let progress = howudoin::fetch();
84/// ```
85pub fn fetch() -> Option<Vec<report::Progress>> {
86    let (tx, rx) = bounded(1);
87    TX.send(|| Fetch(tx));
88    rx.recv_timeout(Duration::from_millis(500)).ok()
89}
90
91/// Flag for cancellation.
92pub fn cancel() {
93    TX.send(|| Cancel);
94}
95
96/// Check the cancellation flag.
97///
98/// If the progress reporter has not been [`init`]ialised, `None` is returned.
99pub fn cancelled() -> Option<bool> {
100    let (tx, rx) = bounded(1);
101    TX.send(|| Cancelled(tx));
102    rx.recv_timeout(Duration::from_millis(500)).ok()
103}
104
105/// Reset the progress consumer loop.
106///
107/// This resets all the tracked progress, but keeps the consumer loop alive (as opposed to stopping
108/// it with [`disable`]).
109/// Note that it is usually preferable to initialise a new loop with a fresh consumer.
110pub fn reset() {
111    TX.send(|| Reset)
112}
113
114pub struct StaticTx(RwLock<Option<Sender<Payload>>>);
115
116impl StaticTx {
117    const fn none() -> Self {
118        StaticTx(parking_lot::const_rwlock(None))
119    }
120
121    fn set_tx(&self, tx: Sender<Payload>) {
122        *self.0.write() = Some(tx);
123    }
124
125    fn disable(&self) {
126        *self.0.write() = None;
127    }
128
129    fn send<F: FnOnce() -> Payload>(&self, payload: F) {
130        match &*self.0.read() {
131            Some(tx) if !tx.is_disconnected() => tx.send(payload()).ok(),
132            _ => Some(()),
133        };
134    }
135}
136
137/// The progress reporter transmitter.
138#[derive(Copy, Clone, PartialEq, Eq)]
139pub struct Tx {
140    id: Id,
141}
142
143impl Tx {
144    /// The distinct ID.
145    pub fn id(&self) -> Id {
146        self.id
147    }
148
149    /// Set the label/name of the report.
150    ///
151    /// ```rust
152    /// howudoin::new().label("Report A");
153    /// ```
154    pub fn label<L: Into<String>>(self, label: L) -> Self {
155        TX.send(|| SetLabel(self.id, label.into()));
156        self
157    }
158
159    /// Set an upper bound on the progress.
160    ///
161    /// If the progress is indeterminate, `None` can be specified.
162    ///
163    /// ```rust
164    /// howudoin::new().set_len(100);
165    /// ```
166    pub fn set_len<L: Into<Option<u64>>>(self, len: L) -> Self {
167        TX.send(|| SetLen(self.id, len.into()));
168        self
169    }
170
171    /// Flag to format the position as bytes.
172    pub fn fmt_as_bytes(self, fmt_as_bytes: bool) -> Self {
173        TX.send(|| SetFmtBytes(self.id, fmt_as_bytes));
174        self
175    }
176
177    /// Set the report message.
178    ///
179    /// ```rust
180    /// let a = howudoin::new();
181    /// a.desc("processing");
182    /// ```
183    pub fn desc<D: Into<String>>(&self, desc: D) -> &Self {
184        TX.send(|| SetDesc(self.id, desc.into()));
185        self
186    }
187
188    /// Increment the report 1 position.
189    ///
190    /// ```rust
191    /// let a = howudoin::new();
192    /// a.inc();
193    /// ```
194    pub fn inc(&self) -> &Self {
195        TX.send(|| Inc(self.id, 1));
196        self
197    }
198
199    /// Increment the report position by `delta`.
200    ///
201    /// ```rust
202    /// let a = howudoin::new();
203    /// a.inc_by(5_u8);
204    /// ```
205    pub fn inc_by<P: Into<u64>>(&self, delta: P) -> &Self {
206        TX.send(|| Inc(self.id, delta.into()));
207        self
208    }
209
210    /// Set the report position.
211    ///
212    /// ```rust
213    /// let a = howudoin::new();
214    /// a.set_pos(5_u8);
215    /// ```
216    pub fn set_pos<P: Into<u64>>(&self, pos: P) -> &Self {
217        TX.send(|| SetPos(self.id, pos.into()));
218        self
219    }
220
221    /// Add an error message.
222    ///
223    /// ```rust
224    /// let a = howudoin::new();
225    /// a.add_err("fail!");
226    /// ```
227    pub fn add_err<M: Into<String>>(&self, msg: M) -> &Self {
228        self.add_accum(report::Severity::Error, msg)
229    }
230
231    /// Add an warning message.
232    ///
233    /// ```rust
234    /// let a = howudoin::new();
235    /// a.add_warn("careful...");
236    /// ```
237    pub fn add_warn<M: Into<String>>(&self, msg: M) -> &Self {
238        self.add_accum(report::Severity::Warn, msg)
239    }
240
241    /// Add an information message.
242    ///
243    /// ```rust
244    /// let a = howudoin::new();
245    /// a.add_info("hello");
246    /// ```
247    pub fn add_info<M: Into<String>>(&self, msg: M) -> &Self {
248        self.add_accum(report::Severity::Info, msg)
249    }
250
251    /// Add an accumulation message.
252    ///
253    /// These messages are accumulated against a progress report, and consumers can display them
254    /// for additional information.
255    pub fn add_accum<M: Into<String>>(&self, severity: report::Severity, msg: M) -> &Self {
256        TX.send(|| Accum(self.id, severity, msg.into()));
257        self
258    }
259
260    /// Check if the consumer loop has been flagged for cancellation.
261    ///
262    /// It is up to the producer to decide what to do if cancellation is detected.
263    pub fn cancelled(&self) -> bool {
264        cancelled().unwrap_or(false)
265    }
266
267    /// Mark this report as finished but should be kept displayed.
268    pub fn finish(self) {
269        TX.send(|| Finish(self.id))
270    }
271
272    /// Mark this report as finished and should be removed from display.
273    pub fn close(self) {
274        TX.send(|| Close(self.id))
275    }
276}