agentmux_srv\backend/
utilds.rs1#![allow(dead_code)]
2use std::io::{self, BufRead, BufReader, Read};
12use std::sync::Mutex;
13
14const DEFAULT_MAX_LINES: usize = 1000;
18
19type LineCallback = Box<dyn Fn(&str) + Send>;
21
22pub struct ReaderLineBuffer<R: Read> {
29 inner: Mutex<ReaderLineBufferInner<R>>,
30}
31
32struct ReaderLineBufferInner<R: Read> {
33 reader: BufReader<R>,
34 lines: Vec<String>,
35 max_lines: usize,
36 total_line_count: usize,
37 done: bool,
38 line_callback: Option<LineCallback>,
39}
40
41impl<R: Read> ReaderLineBuffer<R> {
42 pub fn new(reader: R, max_lines: usize) -> Self {
47 let max = if max_lines == 0 {
48 DEFAULT_MAX_LINES
49 } else {
50 max_lines
51 };
52 Self {
53 inner: Mutex::new(ReaderLineBufferInner {
54 reader: BufReader::new(reader),
55 lines: Vec::with_capacity(max),
56 max_lines: max,
57 total_line_count: 0,
58 done: false,
59 line_callback: None,
60 }),
61 }
62 }
63
64 pub fn set_line_callback<F>(&self, callback: F)
66 where
67 F: Fn(&str) + Send + 'static,
68 {
69 self.inner.lock().unwrap().line_callback = Some(Box::new(callback));
70 }
71
72 pub fn read_line(&self) -> io::Result<String> {
77 let mut inner = self.inner.lock().unwrap();
78 if inner.done {
79 return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "reader done"));
80 }
81
82 let mut line = String::new();
83 match inner.reader.read_line(&mut line) {
84 Ok(0) => {
85 inner.done = true;
86 Err(io::Error::new(io::ErrorKind::UnexpectedEof, "EOF"))
87 }
88 Ok(_) => {
89 let trimmed = line.trim_end_matches('\n').trim_end_matches('\r');
91 let owned = trimmed.to_string();
92 inner.add_line(&owned);
93 Ok(owned)
94 }
95 Err(e) => {
96 inner.done = true;
97 Err(e)
98 }
99 }
100 }
101
102 pub fn read_all(&self) {
104 loop {
105 if self.read_line().is_err() {
106 break;
107 }
108 }
109 }
110
111 pub fn is_done(&self) -> bool {
113 self.inner.lock().unwrap().done
114 }
115
116 pub fn get_lines(&self) -> Vec<String> {
118 self.inner.lock().unwrap().lines.clone()
119 }
120
121 pub fn get_line_count(&self) -> usize {
123 self.inner.lock().unwrap().lines.len()
124 }
125
126 pub fn get_total_line_count(&self) -> usize {
128 self.inner.lock().unwrap().total_line_count
129 }
130}
131
132impl<R: Read> ReaderLineBufferInner<R> {
133 fn add_line(&mut self, line: &str) {
134 if self.lines.len() >= self.max_lines {
135 self.lines.remove(0);
136 }
137 self.lines.push(line.to_string());
138 self.total_line_count += 1;
139
140 if let Some(ref cb) = self.line_callback {
141 cb(line);
142 }
143 }
144}
145
146#[cfg(test)]
149mod tests {
150 use super::*;
151 use std::io::Cursor;
152 use std::sync::Arc;
153
154 #[test]
155 fn test_read_single_line() {
156 let data = Cursor::new("hello\n");
157 let buf = ReaderLineBuffer::new(data, 10);
158
159 let line = buf.read_line().unwrap();
160 assert_eq!(line, "hello");
161 assert_eq!(buf.get_line_count(), 1);
162 assert_eq!(buf.get_total_line_count(), 1);
163 }
164
165 #[test]
166 fn test_read_multiple_lines() {
167 let data = Cursor::new("line1\nline2\nline3\n");
168 let buf = ReaderLineBuffer::new(data, 10);
169
170 assert_eq!(buf.read_line().unwrap(), "line1");
171 assert_eq!(buf.read_line().unwrap(), "line2");
172 assert_eq!(buf.read_line().unwrap(), "line3");
173 assert!(buf.read_line().is_err());
174 assert!(buf.is_done());
175 }
176
177 #[test]
178 fn test_read_all() {
179 let data = Cursor::new("a\nb\nc\n");
180 let buf = ReaderLineBuffer::new(data, 10);
181
182 buf.read_all();
183
184 assert!(buf.is_done());
185 let lines = buf.get_lines();
186 assert_eq!(lines, vec!["a", "b", "c"]);
187 assert_eq!(buf.get_total_line_count(), 3);
188 }
189
190 #[test]
191 fn test_circular_buffer() {
192 let data = Cursor::new("1\n2\n3\n4\n5\n");
193 let buf = ReaderLineBuffer::new(data, 3);
194
195 buf.read_all();
196
197 let lines = buf.get_lines();
198 assert_eq!(lines, vec!["3", "4", "5"]); assert_eq!(buf.get_line_count(), 3);
200 assert_eq!(buf.get_total_line_count(), 5);
201 }
202
203 #[test]
204 fn test_default_max_lines() {
205 let data = Cursor::new("test\n");
206 let buf = ReaderLineBuffer::new(data, 0); buf.read_all();
209 assert_eq!(buf.get_total_line_count(), 1);
211 }
212
213 #[test]
214 fn test_line_callback() {
215 let data = Cursor::new("hello\nworld\n");
216 let buf = ReaderLineBuffer::new(data, 10);
217
218 let collected = Arc::new(Mutex::new(Vec::<String>::new()));
219 let collected_clone = collected.clone();
220 buf.set_line_callback(move |line| {
221 collected_clone.lock().unwrap().push(line.to_string());
222 });
223
224 buf.read_all();
225
226 let lines = collected.lock().unwrap();
227 assert_eq!(*lines, vec!["hello", "world"]);
228 }
229
230 #[test]
231 fn test_empty_input() {
232 let data = Cursor::new("");
233 let buf = ReaderLineBuffer::new(data, 10);
234
235 assert!(buf.read_line().is_err());
236 assert!(buf.is_done());
237 assert_eq!(buf.get_line_count(), 0);
238 }
239
240 #[test]
241 fn test_no_trailing_newline() {
242 let data = Cursor::new("no newline at end");
243 let buf = ReaderLineBuffer::new(data, 10);
244
245 let line = buf.read_line().unwrap();
246 assert_eq!(line, "no newline at end");
247 }
248
249 #[test]
250 fn test_windows_line_endings() {
251 let data = Cursor::new("line1\r\nline2\r\n");
252 let buf = ReaderLineBuffer::new(data, 10);
253
254 buf.read_all();
255 let lines = buf.get_lines();
256 assert_eq!(lines, vec!["line1", "line2"]);
257 }
258
259 #[test]
260 fn test_get_lines_returns_copy() {
261 let data = Cursor::new("a\nb\n");
262 let buf = ReaderLineBuffer::new(data, 10);
263
264 buf.read_all();
265 let lines1 = buf.get_lines();
266 let lines2 = buf.get_lines();
267 assert_eq!(lines1, lines2);
268 }
269}