thunderstore_engine/progress.rs
1//! UI-agnostic progress reporting.
2//!
3//! The engine performs long-running network and IO work (manifest downloads,
4//! concurrent package downloads) but must not depend on any particular UI
5//! toolkit. Callers provide a [`ProgressReporter`]; a CLI can wire it to
6//! `indicatif`, a GUI can wire it to reactive signals, and tests can use the
7//! default [`NoopProgress`].
8
9#![deny(missing_docs)]
10
11/// Factory for per-unit-of-work progress handles.
12///
13/// Implementations are shared across concurrent tasks, so this trait requires
14/// `Send + Sync`.
15pub trait ProgressReporter: Send + Sync {
16 /// Begins reporting a new unit of work.
17 ///
18 /// `total` is the expected number of units (e.g. bytes); `None` indicates an
19 /// indeterminate amount of work (render as a spinner).
20 fn begin(&self, label: &str, total: Option<u64>) -> Box<dyn ProgressHandle>;
21}
22
23/// A handle to a single in-flight unit of work.
24///
25/// The engine calls [`finish`](ProgressHandle::finish) exactly once when the work
26/// ends — whether it succeeded or failed — so an implementation can rely on
27/// `finish` to clear or finalize its display and should not leave a bar dangling
28/// if it is never called again.
29///
30/// Handles are `Send + Sync`: a single handle is shared by reference across the
31/// retried attempts of one download, and that download future must stay `Send` so
32/// callers can drive it from a multi-threaded runtime (e.g. `tokio::spawn`).
33pub trait ProgressHandle: Send + Sync {
34 /// Advances progress by `delta` units.
35 fn inc(&self, delta: u64);
36
37 /// Sets (or updates) the total number of units when it only becomes known
38 /// after the handle was created — e.g. a download's `Content-Length` arriving
39 /// with the response. Implementations that cannot re-total may ignore this;
40 /// the default does nothing.
41 fn set_length(&self, _total: u64) {}
42
43 /// Resets (overrides) the absolute progress position to `position` units.
44 ///
45 /// Unlike [`inc`](ProgressHandle::inc), which advances relative to the current
46 /// position, this sets it directly — used to rewind to zero when a download is
47 /// retried so re-downloaded bytes are not double-counted and the bar cannot
48 /// overshoot its total. Implementations that cannot re-position may ignore this;
49 /// the default does nothing.
50 fn set_position(&self, _position: u64) {}
51
52 /// Updates the displayed message for this unit of work.
53 fn set_message(&self, msg: &str);
54
55 /// Marks this unit of work complete, optionally with a final message. Called
56 /// exactly once per handle, on both the success and failure paths.
57 fn finish(&self, msg: Option<&str>);
58}
59
60/// A [`ProgressReporter`] that discards all progress updates.
61///
62/// This is the default used by [`crate::client::ThunderstoreClientBuilder`] when
63/// no reporter is supplied.
64pub struct NoopProgress;
65
66impl ProgressReporter for NoopProgress {
67 fn begin(&self, _label: &str, _total: Option<u64>) -> Box<dyn ProgressHandle> {
68 Box::new(NoopHandle)
69 }
70}
71
72/// A no-op [`ProgressHandle`] returned by [`NoopProgress`].
73struct NoopHandle;
74
75impl ProgressHandle for NoopHandle {
76 fn inc(&self, _delta: u64) {}
77 fn set_message(&self, _msg: &str) {}
78 fn finish(&self, _msg: Option<&str>) {}
79}