Skip to main content

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}