gardesk/garnotify / af40f03

Browse files

notification: add history buffer

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
af40f03c8b54e03b822dfd1974d9931201be5f5c
Parents
44101a5
Tree
a9ecbfd

1 changed file

StatusFile+-
A garnotify/src/notification/history.rs 164 0
garnotify/src/notification/history.rsadded
@@ -0,0 +1,164 @@
1
+//! Notification history management
2
+
3
+use std::collections::VecDeque;
4
+use tracing::debug;
5
+
6
+use super::types::Notification;
7
+
8
+/// Notification history with circular buffer
9
+pub struct History {
10
+    /// Stored notifications (most recent at back)
11
+    items: VecDeque<Notification>,
12
+    /// Maximum number of items to keep
13
+    max_length: usize,
14
+}
15
+
16
+impl History {
17
+    /// Create a new history with the given maximum length
18
+    pub fn new(max_length: usize) -> Self {
19
+        Self {
20
+            items: VecDeque::with_capacity(max_length.min(100)),
21
+            max_length,
22
+        }
23
+    }
24
+
25
+    /// Add a notification to history
26
+    ///
27
+    /// If history is at capacity, the oldest item is removed.
28
+    pub fn push(&mut self, notification: Notification) {
29
+        // Check if notification is transient (should not be stored)
30
+        if notification.hints.transient {
31
+            debug!(
32
+                "Skipping transient notification {} from history",
33
+                notification.id
34
+            );
35
+            return;
36
+        }
37
+
38
+        debug!(
39
+            "Adding notification {} to history (current size: {})",
40
+            notification.id,
41
+            self.items.len()
42
+        );
43
+
44
+        // Remove oldest if at capacity
45
+        if self.items.len() >= self.max_length {
46
+            self.items.pop_front();
47
+        }
48
+
49
+        self.items.push_back(notification);
50
+    }
51
+
52
+    /// Pop the most recent notification from history
53
+    pub fn pop(&mut self) -> Option<Notification> {
54
+        let notification = self.items.pop_back();
55
+        if let Some(ref n) = notification {
56
+            debug!("Popped notification {} from history", n.id);
57
+        }
58
+        notification
59
+    }
60
+
61
+    /// Get the most recent notification without removing it
62
+    pub fn peek(&self) -> Option<&Notification> {
63
+        self.items.back()
64
+    }
65
+
66
+    /// Clear all history
67
+    pub fn clear(&mut self) {
68
+        debug!("Clearing {} notifications from history", self.items.len());
69
+        self.items.clear();
70
+    }
71
+
72
+    /// Get the number of items in history
73
+    pub fn len(&self) -> usize {
74
+        self.items.len()
75
+    }
76
+
77
+    /// Check if history is empty
78
+    pub fn is_empty(&self) -> bool {
79
+        self.items.is_empty()
80
+    }
81
+
82
+    /// Get all items in history (oldest first)
83
+    pub fn list(&self) -> &VecDeque<Notification> {
84
+        &self.items
85
+    }
86
+
87
+    /// Get items as a vector (most recent first)
88
+    pub fn list_recent_first(&self) -> Vec<&Notification> {
89
+        self.items.iter().rev().collect()
90
+    }
91
+}
92
+
93
+impl Default for History {
94
+    fn default() -> Self {
95
+        Self::new(100)
96
+    }
97
+}
98
+
99
+#[cfg(test)]
100
+mod tests {
101
+    use super::*;
102
+    use crate::notification::types::{Action, Hints};
103
+
104
+    fn make_notification(id: u32, summary: &str) -> Notification {
105
+        Notification::new(
106
+            id,
107
+            "test".into(),
108
+            0,
109
+            "".into(),
110
+            summary.into(),
111
+            "body".into(),
112
+            vec![],
113
+            Hints::default(),
114
+            5000,
115
+        )
116
+    }
117
+
118
+    #[test]
119
+    fn test_push_pop() {
120
+        let mut history = History::new(10);
121
+
122
+        history.push(make_notification(1, "first"));
123
+        history.push(make_notification(2, "second"));
124
+
125
+        assert_eq!(history.len(), 2);
126
+
127
+        let popped = history.pop().unwrap();
128
+        assert_eq!(popped.id, 2);
129
+        assert_eq!(popped.summary, "second");
130
+
131
+        let popped = history.pop().unwrap();
132
+        assert_eq!(popped.id, 1);
133
+
134
+        assert!(history.is_empty());
135
+    }
136
+
137
+    #[test]
138
+    fn test_max_length() {
139
+        let mut history = History::new(3);
140
+
141
+        history.push(make_notification(1, "first"));
142
+        history.push(make_notification(2, "second"));
143
+        history.push(make_notification(3, "third"));
144
+        history.push(make_notification(4, "fourth"));
145
+
146
+        assert_eq!(history.len(), 3);
147
+
148
+        // Oldest (id=1) should have been removed
149
+        let items: Vec<_> = history.list().iter().map(|n| n.id).collect();
150
+        assert_eq!(items, vec![2, 3, 4]);
151
+    }
152
+
153
+    #[test]
154
+    fn test_transient_not_stored() {
155
+        let mut history = History::new(10);
156
+
157
+        let mut notification = make_notification(1, "transient");
158
+        notification.hints.transient = true;
159
+
160
+        history.push(notification);
161
+
162
+        assert!(history.is_empty());
163
+    }
164
+}