1use notify_rust::{Notification, Timeout};
2use std::sync::Arc;
3
4pub trait NotificationClient: Send + Sync {
6 fn notify_task_complete(&self, task_name: &str, success: bool, message: Option<&str>);
8
9 fn notify_all_tasks_complete(&self, total: usize, succeeded: usize, failed: usize);
11}
12
13pub struct DesktopNotificationClient {
15 timeout_seconds: u32,
16}
17
18impl DesktopNotificationClient {
19 pub fn new(timeout_seconds: u32) -> Self {
21 Self { timeout_seconds }
22 }
23}
24
25impl NotificationClient for DesktopNotificationClient {
26 fn notify_task_complete(&self, task_name: &str, success: bool, message: Option<&str>) {
27 let summary = if success {
28 "Task Completed"
29 } else {
30 "Task Failed"
31 };
32 let body = format!(
33 "Task '{}' has {}\n{}",
34 task_name,
35 if success {
36 "completed successfully"
37 } else {
38 "failed"
39 },
40 message.unwrap_or("")
41 );
42
43 let mut notification = Notification::new();
44 notification.summary(summary);
45 notification.body(&body);
46 notification.timeout(Timeout::Milliseconds(self.timeout_seconds * 1000));
47
48 if let Err(e) = notification.show() {
49 eprintln!("TSK: {} - {}", summary, body.replace('\n', " "));
51 eprintln!("(Desktop notification failed: {e})");
52 }
53 }
54
55 fn notify_all_tasks_complete(&self, total: usize, succeeded: usize, failed: usize) {
56 let summary = if failed == 0 {
57 "All Tasks Completed"
58 } else {
59 "Tasks Completed with Failures"
60 };
61
62 let body = format!("Total: {total}\nSucceeded: {succeeded}\nFailed: {failed}");
63
64 let mut notification = Notification::new();
65 notification.summary(summary);
66 notification.body(&body);
67 notification.timeout(Timeout::Milliseconds(self.timeout_seconds * 1000));
68
69 if let Err(e) = notification.show() {
70 eprintln!("TSK: {} - {}", summary, body.replace('\n', " "));
72 eprintln!("(Desktop notification failed: {e})");
73 }
74 }
75}
76
77pub struct NoOpNotificationClient;
79
80impl NotificationClient for NoOpNotificationClient {
81 fn notify_task_complete(&self, _task_name: &str, _success: bool, _message: Option<&str>) {
82 }
84
85 fn notify_all_tasks_complete(&self, _total: usize, _succeeded: usize, _failed: usize) {
86 }
88}
89
90pub fn create_notification_client() -> Arc<dyn NotificationClient> {
92 if std::env::var("TSK_NO_NOTIFICATIONS").is_ok() || cfg!(test) {
94 Arc::new(NoOpNotificationClient)
95 } else {
96 Arc::new(DesktopNotificationClient::new(5))
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn test_noop_notification_client() {
106 let client = NoOpNotificationClient;
107 client.notify_task_complete("test", true, None);
109 client.notify_task_complete("test", false, Some("error"));
110 client.notify_all_tasks_complete(5, 3, 2);
111 }
112
113 #[test]
114 fn test_create_notification_client_in_tests() {
115 let client = create_notification_client();
116 client.notify_task_complete("test", true, None);
118 }
119}