Skip to main content

oxihuman_core/
notification_queue.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Notification queue with priority.
6
7use std::cmp::Ordering;
8use std::collections::BinaryHeap;
9
10/// Priority level for notifications.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum NotifPriority {
13    Low = 0,
14    Normal = 1,
15    High = 2,
16    Critical = 3,
17}
18
19/// A notification item.
20#[derive(Debug, Clone)]
21pub struct Notification {
22    pub id: u64,
23    pub priority: NotifPriority,
24    pub title: String,
25    pub body: String,
26}
27
28#[derive(Debug)]
29struct PrioritizedNotif {
30    notif: Notification,
31}
32
33impl PartialEq for PrioritizedNotif {
34    fn eq(&self, other: &Self) -> bool {
35        self.notif.priority as u8 == other.notif.priority as u8 && self.notif.id == other.notif.id
36    }
37}
38impl Eq for PrioritizedNotif {}
39
40impl PartialOrd for PrioritizedNotif {
41    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
42        Some(self.cmp(other))
43    }
44}
45
46impl Ord for PrioritizedNotif {
47    fn cmp(&self, other: &Self) -> Ordering {
48        (self.notif.priority as u8)
49            .cmp(&(other.notif.priority as u8))
50            .then(other.notif.id.cmp(&self.notif.id))
51    }
52}
53
54/// Priority-ordered notification queue.
55#[derive(Debug, Default)]
56pub struct NotificationQueue {
57    heap: BinaryHeap<PrioritizedNotif>,
58    next_id: u64,
59}
60
61impl NotificationQueue {
62    pub fn new() -> Self {
63        Self::default()
64    }
65
66    pub fn push(&mut self, priority: NotifPriority, title: &str, body: &str) -> u64 {
67        let id = self.next_id;
68        self.next_id += 1;
69        self.heap.push(PrioritizedNotif {
70            notif: Notification {
71                id,
72                priority,
73                title: title.to_string(),
74                body: body.to_string(),
75            },
76        });
77        id
78    }
79
80    pub fn pop(&mut self) -> Option<Notification> {
81        self.heap.pop().map(|p| p.notif)
82    }
83
84    pub fn peek_priority(&self) -> Option<NotifPriority> {
85        self.heap.peek().map(|p| p.notif.priority)
86    }
87
88    pub fn len(&self) -> usize {
89        self.heap.len()
90    }
91
92    pub fn is_empty(&self) -> bool {
93        self.heap.is_empty()
94    }
95}
96
97pub fn new_notification_queue() -> NotificationQueue {
98    NotificationQueue::new()
99}
100
101pub fn nq_push(q: &mut NotificationQueue, priority: NotifPriority, title: &str, body: &str) -> u64 {
102    q.push(priority, title, body)
103}
104
105pub fn nq_pop(q: &mut NotificationQueue) -> Option<Notification> {
106    q.pop()
107}
108
109pub fn nq_len(q: &NotificationQueue) -> usize {
110    q.len()
111}
112
113pub fn nq_peek_priority(q: &NotificationQueue) -> Option<NotifPriority> {
114    q.peek_priority()
115}
116
117pub fn nq_is_empty(q: &NotificationQueue) -> bool {
118    q.is_empty()
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn test_push_and_len() {
127        let mut q = new_notification_queue();
128        nq_push(&mut q, NotifPriority::Normal, "Hello", "World");
129        assert_eq!(nq_len(&q), 1);
130    }
131
132    #[test]
133    fn test_pop_returns_item() {
134        let mut q = new_notification_queue();
135        nq_push(&mut q, NotifPriority::Normal, "T", "B");
136        let n = nq_pop(&mut q).expect("should succeed");
137        assert_eq!(n.title, "T");
138    }
139
140    #[test]
141    fn test_priority_ordering() {
142        /* critical should come out first */
143        let mut q = new_notification_queue();
144        nq_push(&mut q, NotifPriority::Low, "low", "");
145        nq_push(&mut q, NotifPriority::Critical, "crit", "");
146        nq_push(&mut q, NotifPriority::Normal, "norm", "");
147        let first = nq_pop(&mut q).expect("should succeed");
148        assert_eq!(first.priority, NotifPriority::Critical);
149    }
150
151    #[test]
152    fn test_peek_priority() {
153        let mut q = new_notification_queue();
154        nq_push(&mut q, NotifPriority::High, "h", "");
155        assert_eq!(nq_peek_priority(&q), Some(NotifPriority::High));
156    }
157
158    #[test]
159    fn test_is_empty_initially() {
160        let q = new_notification_queue();
161        assert!(nq_is_empty(&q));
162    }
163
164    #[test]
165    fn test_pop_empty_returns_none() {
166        let mut q = new_notification_queue();
167        assert!(nq_pop(&mut q).is_none());
168    }
169
170    #[test]
171    fn test_id_increments() {
172        let mut q = new_notification_queue();
173        let id0 = nq_push(&mut q, NotifPriority::Low, "a", "");
174        let id1 = nq_push(&mut q, NotifPriority::Low, "b", "");
175        assert_ne!(id0, id1);
176    }
177
178    #[test]
179    fn test_multiple_pops_drain_queue() {
180        let mut q = new_notification_queue();
181        nq_push(&mut q, NotifPriority::Low, "a", "");
182        nq_push(&mut q, NotifPriority::Low, "b", "");
183        nq_pop(&mut q);
184        nq_pop(&mut q);
185        assert!(nq_is_empty(&q));
186    }
187
188    #[test]
189    fn test_high_before_normal() {
190        let mut q = new_notification_queue();
191        nq_push(&mut q, NotifPriority::Normal, "n", "");
192        nq_push(&mut q, NotifPriority::High, "h", "");
193        let first = nq_pop(&mut q).expect("should succeed");
194        assert_eq!(first.priority, NotifPriority::High);
195    }
196}