logger_bro/client_reporter.rs
1/// ==============================================================================
2/// src/client_reporter.rs
3/// Client-side reporting API for sending progress updates to the store.
4/// ==============================================================================
5
6use std::sync::mpsc::Sender;
7
8use std::time::Instant;
9
10use crate::{ClientState, TaskId, TaskStatus};
11
12/// Errors that can occur when sending updates from a client thread.
13#[derive(Debug)]
14pub enum ReportError {
15 /// The receiver/store side has been dropped, so updates can no longer be sent.
16 Closed,
17}
18
19/// Lightweight client-side handle for sending updates into the store.
20///
21/// This type is intended to be cloned and moved across threads without
22/// carrying any store internals.
23#[derive(Clone)]
24pub struct ClientReporter {
25 tx: Sender<ClientState>,
26}
27
28/// Handle for a single client/task instance.
29///
30/// The handle owns the task identity and start time, and emits partial
31/// updates using those fields as a stable base.
32#[derive(Clone)]
33pub struct ClientHandle {
34 reporter: ClientReporter,
35 id: TaskId,
36 start_time: Instant,
37}
38
39impl ClientReporter {
40 /// Create a reporter from a sender that feeds the store.
41 pub fn new(tx: Sender<ClientState>) -> Self {
42 Self { tx }
43 }
44
45 /// Send a raw `ClientState` update to the store.
46 ///
47 /// This is the lowest-level API; most users should prefer `start`
48 /// and the `ClientHandle` methods.
49 pub fn report(&self, state: ClientState) -> Result<(), ReportError> {
50 self.tx.send(state).map_err(|_| ReportError::Closed)
51 }
52
53 /// Start a new client/task and return a handle for future updates.
54 ///
55 /// This sends an initial full state (including label and optional total)
56 /// and returns a handle that emits partial updates afterwards.
57 pub fn start(
58 &self,
59 label: impl Into<String>,
60 total: Option<u64>,
61 ) -> Result<ClientHandle, ReportError> {
62 let state = ClientState::new(label, total);
63 let handle = ClientHandle {
64 reporter: self.clone(),
65 id: state.id,
66 start_time: state.start_time,
67 };
68 self.report(state)?;
69 Ok(handle)
70 }
71}
72
73impl ClientHandle {
74 /// Return the internal identifier for this client/task.
75 pub fn id(&self) -> TaskId {
76 self.id
77 }
78
79 /// Update the display label for this client/task.
80 pub fn set_label(&self, label: impl Into<String>) -> Result<(), ReportError> {
81 let mut update = self.base_update();
82 update.label = Some(label.into());
83 self.reporter.report(update)
84 }
85
86 /// Update the total units of work for this client/task.
87 ///
88 /// Use `None` for an indeterminate total.
89 pub fn set_total(&self, total: Option<u64>) -> Result<(), ReportError> {
90 let mut update = self.base_update();
91 update.total = total;
92 self.reporter.report(update)
93 }
94
95 /// Set the current completed units of work.
96 pub fn set_current(&self, current: u64) -> Result<(), ReportError> {
97 let mut update = self.base_update();
98 update.current = Some(current);
99 self.reporter.report(update)
100 }
101
102 /// Mark this client/task as completed.
103 pub fn complete(&self) -> Result<(), ReportError> {
104 self.set_status(TaskStatus::Completed)
105 }
106
107 /// Mark this client/task as failed.
108 pub fn fail(&self) -> Result<(), ReportError> {
109 self.set_status(TaskStatus::Failed)
110 }
111
112 /// Mark this client/task as canceled.
113 pub fn cancel(&self) -> Result<(), ReportError> {
114 self.set_status(TaskStatus::Canceled)
115 }
116
117 /// Internal helper to set a status update.
118 fn set_status(&self, status: TaskStatus) -> Result<(), ReportError> {
119 let mut update = self.base_update();
120 update.status = Some(status);
121 self.reporter.report(update)
122 }
123
124 /// Construct a minimal update payload with identity and timestamps.
125 ///
126 /// The store will merge this partial state with the existing one.
127 fn base_update(&self) -> ClientState {
128 ClientState::partial(self.id, self.start_time, Instant::now())
129 }
130}