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}