Skip to main content

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}