1use std::collections::VecDeque;
10
11#[derive(Copy, Clone, Debug, PartialEq, Eq)]
12pub enum Policy {
13 Enqueue,
15 Interrupt,
17 Replace,
19 Front,
21}
22
23#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
24pub enum Source {
25 #[default]
27 DBus,
28 Cli,
29 Hotkey,
30 Notification,
31}
32
33impl Source {
34 pub fn default_policy(self) -> Policy {
38 match self {
39 Source::DBus | Source::Cli => Policy::Enqueue,
40 Source::Hotkey => Policy::Replace,
41 Source::Notification => Policy::Front,
42 }
43 }
44}
45
46#[derive(Clone, Debug, PartialEq)]
47pub struct Utterance {
48 pub id: u64,
49 pub text: String,
50 pub voice: String,
51 pub speed: f32,
52 pub source: Source,
53}
54
55pub struct Queue {
56 items: VecDeque<Utterance>,
57 next_id: u64,
58}
59
60impl Queue {
61 pub fn new() -> Self {
62 Queue { items: VecDeque::new(), next_id: 1 }
63 }
64
65 pub fn next_id(&mut self) -> u64 {
66 let id = self.next_id;
67 self.next_id += 1;
68 id
69 }
70
71 pub fn submit(&mut self, u: Utterance, policy: Policy) -> Vec<u64> {
74 let mut dropped = Vec::new();
75 match policy {
76 Policy::Enqueue => self.items.push_back(u),
77 Policy::Interrupt | Policy::Front => self.items.push_front(u),
78 Policy::Replace => {
79 dropped = self.clear();
80 self.items.push_back(u);
81 }
82 }
83 dropped
84 }
85
86 pub fn pop_front(&mut self) -> Option<Utterance> {
87 self.items.pop_front()
88 }
89
90 pub fn clear(&mut self) -> Vec<u64> {
91 let ids = self.items.iter().map(|u| u.id).collect();
92 self.items.clear();
93 ids
94 }
95
96 pub fn cancel(&mut self, id: u64) -> bool {
97 let before = self.items.len();
98 self.items.retain(|u| u.id != id);
99 self.items.len() != before
100 }
101
102 pub fn len(&self) -> usize {
103 self.items.len()
104 }
105
106 pub fn is_empty(&self) -> bool {
107 self.items.is_empty()
108 }
109
110 pub fn iter(&self) -> impl Iterator<Item = &Utterance> {
111 self.items.iter()
112 }
113}
114
115impl Default for Queue {
116 fn default() -> Self {
117 Self::new()
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 fn utt(id: u64, text: &str) -> Utterance {
126 Utterance {
127 id,
128 text: text.into(),
129 voice: "af_heart".into(),
130 speed: 1.0,
131 source: Source::DBus,
132 }
133 }
134
135 fn texts(q: &Queue) -> Vec<String> {
136 q.iter().map(|u| u.text.clone()).collect()
137 }
138
139 #[test]
140 fn enqueue_appends_in_order() {
141 let mut q = Queue::new();
142 q.submit(utt(1, "a"), Policy::Enqueue);
143 q.submit(utt(2, "b"), Policy::Enqueue);
144 assert_eq!(texts(&q), vec!["a", "b"]);
145 }
146
147 #[test]
148 fn front_jumps_the_line_without_dropping_anything() {
149 let mut q = Queue::new();
150 q.submit(utt(1, "a"), Policy::Enqueue);
151 q.submit(utt(2, "b"), Policy::Enqueue);
152 let dropped = q.submit(utt(3, "urgent"), Policy::Front);
153 assert!(dropped.is_empty());
154 assert_eq!(texts(&q), vec!["urgent", "a", "b"]);
155 }
156
157 #[test]
158 fn interrupt_jumps_the_line_and_keeps_pending() {
159 let mut q = Queue::new();
160 q.submit(utt(1, "a"), Policy::Enqueue);
161 let dropped = q.submit(utt(2, "now"), Policy::Interrupt);
162 assert!(dropped.is_empty(), "interrupt drops the *current* utterance, not the queue");
163 assert_eq!(texts(&q), vec!["now", "a"]);
164 }
165
166 #[test]
167 fn replace_clears_everything_pending() {
168 let mut q = Queue::new();
169 q.submit(utt(1, "a"), Policy::Enqueue);
170 q.submit(utt(2, "b"), Policy::Enqueue);
171 let dropped = q.submit(utt(3, "only"), Policy::Replace);
172 assert_eq!(dropped, vec![1, 2]);
173 assert_eq!(texts(&q), vec!["only"]);
174 }
175
176 #[test]
177 fn source_defaults_match_the_spec() {
178 assert_eq!(Source::DBus.default_policy(), Policy::Enqueue);
179 assert_eq!(Source::Cli.default_policy(), Policy::Enqueue);
180 assert_eq!(Source::Hotkey.default_policy(), Policy::Replace);
181 assert_eq!(Source::Notification.default_policy(), Policy::Front);
182 }
183
184 #[test]
185 fn cancel_removes_one_by_id() {
186 let mut q = Queue::new();
187 q.submit(utt(1, "a"), Policy::Enqueue);
188 q.submit(utt(2, "b"), Policy::Enqueue);
189 assert!(q.cancel(1));
190 assert_eq!(texts(&q), vec!["b"]);
191 assert!(!q.cancel(99), "cancelling an unknown id reports false");
192 }
193
194 #[test]
195 fn clear_returns_the_dropped_ids() {
196 let mut q = Queue::new();
197 q.submit(utt(1, "a"), Policy::Enqueue);
198 q.submit(utt(2, "b"), Policy::Enqueue);
199 assert_eq!(q.clear(), vec![1, 2]);
200 assert!(q.is_empty());
201 }
202
203 #[test]
204 fn pop_front_drains_in_order() {
205 let mut q = Queue::new();
206 q.submit(utt(1, "a"), Policy::Enqueue);
207 q.submit(utt(2, "b"), Policy::Enqueue);
208 assert_eq!(q.pop_front().map(|u| u.text), Some("a".into()));
209 assert_eq!(q.pop_front().map(|u| u.text), Some("b".into()));
210 assert_eq!(q.pop_front(), None);
211 }
212
213 #[test]
214 fn ids_are_unique_and_increasing() {
215 let mut q = Queue::new();
216 let a = q.next_id();
217 let b = q.next_id();
218 assert!(b > a);
219 }
220
221 #[test]
222 fn default_and_new_agree_and_start_at_one() {
223 let mut q_default = Queue::default();
224 let mut q_new = Queue::new();
225 let id_default = q_default.next_id();
226 let id_new = q_new.next_id();
227 assert_eq!(id_default, id_new, "Queue::default() and Queue::new() should produce same first id");
228 assert_ne!(id_default, 0, "first id must not be 0 (sentinel for no utterance)");
229 }
230}