boxmux_lib/
circular_buffer.rs1use std::collections::VecDeque;
2
3#[derive(Debug, Clone)]
6pub struct CircularBuffer {
7 buffer: VecDeque<String>,
8 max_size: usize,
9 total_lines_added: u64, }
11
12impl CircularBuffer {
13 pub fn new(max_size: usize) -> Self {
15 Self {
16 buffer: VecDeque::with_capacity(max_size),
17 max_size,
18 total_lines_added: 0,
19 }
20 }
21
22 pub fn push(&mut self, line: String) {
24 if self.buffer.len() >= self.max_size && self.max_size > 0 {
25 self.buffer.pop_front(); }
27
28 self.buffer.push_back(line);
29 self.total_lines_added += 1;
30 }
31
32 pub fn len(&self) -> usize {
34 self.buffer.len()
35 }
36
37 pub fn is_empty(&self) -> bool {
39 self.buffer.is_empty()
40 }
41
42 pub fn get_all_lines(&self) -> Vec<String> {
44 self.buffer.iter().cloned().collect()
45 }
46
47 pub fn get_last_lines(&self, n: usize) -> Vec<String> {
49 let start_idx = if n >= self.buffer.len() {
50 0
51 } else {
52 self.buffer.len() - n
53 };
54 self.buffer.iter().skip(start_idx).cloned().collect()
55 }
56
57 pub fn get_lines_range(&self, start: usize, count: usize) -> Vec<String> {
59 if start >= self.buffer.len() {
60 return Vec::new();
61 }
62
63 let end = (start + count).min(self.buffer.len());
64 self.buffer.range(start..end).cloned().collect()
65 }
66
67 pub fn get_content(&self) -> String {
69 self.buffer.iter().cloned().collect::<Vec<_>>().join("\n")
70 }
71
72 pub fn get_recent_content(&self, lines: usize) -> String {
74 self.get_last_lines(lines).join("\n")
75 }
76
77 pub fn clear(&mut self) {
79 self.buffer.clear();
80 }
82
83 pub fn max_size(&self) -> usize {
85 self.max_size
86 }
87
88 pub fn set_max_size(&mut self, new_max_size: usize) {
90 self.max_size = new_max_size;
91
92 while self.buffer.len() > new_max_size && new_max_size > 0 {
94 self.buffer.pop_front();
95 }
96
97 if new_max_size > 0 {
99 self.buffer.reserve(new_max_size);
100 }
101 }
102
103 pub fn get_stats(&self) -> BufferStats {
105 BufferStats {
106 current_lines: self.buffer.len(),
107 max_size: self.max_size,
108 total_lines_added: self.total_lines_added,
109 memory_usage_bytes: self.estimate_memory_usage(),
110 is_at_capacity: self.buffer.len() >= self.max_size,
111 }
112 }
113
114 fn estimate_memory_usage(&self) -> usize {
116 let string_overhead = std::mem::size_of::<String>();
117 let content_size: usize = self.buffer.iter().map(|s| s.len()).sum();
118 let deque_overhead =
119 std::mem::size_of::<VecDeque<String>>() + (self.buffer.capacity() * string_overhead);
120
121 content_size + deque_overhead
122 }
123
124 pub fn search(&self, query: &str) -> Vec<(usize, String)> {
126 let query_lower = query.to_lowercase();
127 self.buffer
128 .iter()
129 .enumerate()
130 .filter_map(|(idx, line)| {
131 if line.to_lowercase().contains(&query_lower) {
132 Some((idx, line.clone()))
133 } else {
134 None
135 }
136 })
137 .collect()
138 }
139
140 pub fn get_lines_reverse(&self) -> Vec<String> {
142 self.buffer.iter().rev().cloned().collect()
143 }
144}
145
146#[derive(Debug, Clone)]
148pub struct BufferStats {
149 pub current_lines: usize,
150 pub max_size: usize,
151 pub total_lines_added: u64,
152 pub memory_usage_bytes: usize,
153 pub is_at_capacity: bool,
154}
155
156impl Default for CircularBuffer {
157 fn default() -> Self {
159 Self::new(1000)
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
168 fn test_circular_buffer_basic_operations() {
169 let mut buffer = CircularBuffer::new(3);
170
171 assert_eq!(buffer.len(), 0);
172 assert!(buffer.is_empty());
173
174 buffer.push("line1".to_string());
175 buffer.push("line2".to_string());
176 buffer.push("line3".to_string());
177
178 assert_eq!(buffer.len(), 3);
179 assert!(!buffer.is_empty());
180 assert_eq!(buffer.get_all_lines(), vec!["line1", "line2", "line3"]);
181 }
182
183 #[test]
184 fn test_circular_buffer_overflow() {
185 let mut buffer = CircularBuffer::new(2);
186
187 buffer.push("line1".to_string());
188 buffer.push("line2".to_string());
189 buffer.push("line3".to_string()); assert_eq!(buffer.len(), 2);
192 assert_eq!(buffer.get_all_lines(), vec!["line2", "line3"]);
193
194 buffer.push("line4".to_string()); assert_eq!(buffer.get_all_lines(), vec!["line3", "line4"]);
196 }
197
198 #[test]
199 fn test_circular_buffer_get_last_lines() {
200 let mut buffer = CircularBuffer::new(5);
201
202 for i in 1..=5 {
203 buffer.push(format!("line{}", i));
204 }
205
206 assert_eq!(buffer.get_last_lines(2), vec!["line4", "line5"]);
207 assert_eq!(
208 buffer.get_last_lines(10),
209 vec!["line1", "line2", "line3", "line4", "line5"]
210 );
211 }
212
213 #[test]
214 fn test_circular_buffer_search() {
215 let mut buffer = CircularBuffer::new(5);
216
217 buffer.push("error: file not found".to_string());
218 buffer.push("info: processing data".to_string());
219 buffer.push("warning: deprecated function".to_string());
220 buffer.push("error: connection failed".to_string());
221
222 let results = buffer.search("error");
223 assert_eq!(results.len(), 2);
224 assert_eq!(results[0].0, 0); assert_eq!(results[1].0, 3); }
227
228 #[test]
229 fn test_circular_buffer_resize() {
230 let mut buffer = CircularBuffer::new(5);
231
232 for i in 1..=5 {
233 buffer.push(format!("line{}", i));
234 }
235
236 buffer.set_max_size(3);
238 assert_eq!(buffer.len(), 3);
239 assert_eq!(buffer.get_all_lines(), vec!["line3", "line4", "line5"]);
240 }
241
242 #[test]
243 fn test_circular_buffer_stats() {
244 let mut buffer = CircularBuffer::new(2);
245
246 buffer.push("test1".to_string());
247 buffer.push("test2".to_string());
248 buffer.push("test3".to_string()); let stats = buffer.get_stats();
251 assert_eq!(stats.current_lines, 2);
252 assert_eq!(stats.max_size, 2);
253 assert_eq!(stats.total_lines_added, 3);
254 assert!(stats.is_at_capacity);
255 assert!(stats.memory_usage_bytes > 0);
256 }
257
258 #[test]
259 fn test_circular_buffer_content_methods() {
260 let mut buffer = CircularBuffer::new(3);
261
262 buffer.push("line1".to_string());
263 buffer.push("line2".to_string());
264 buffer.push("line3".to_string());
265
266 assert_eq!(buffer.get_content(), "line1\nline2\nline3");
267 assert_eq!(buffer.get_recent_content(2), "line2\nline3");
268 }
269}