ed_journals/modules/io/models/
log_iter.rs1use crate::logs::LogEvent;
2use crate::modules::io::error::LogIOError;
3use serde::de::DeserializeOwned;
4use std::io::Read;
5use std::marker::PhantomData;
6
7#[derive(Debug)]
26pub struct LogIter<T, R = LogEvent>
27where
28 T: Read,
29 R: DeserializeOwned,
30{
31 inner: T,
32 _p: PhantomData<R>,
33}
34
35impl<T> LogIter<T>
36where
37 T: Read,
38{
39 pub fn new(inner: T) -> LogIter<T, LogEvent> {
42 LogIter::new_typed(inner)
43 }
44
45 pub fn new_raw(inner: T) -> LogIter<T, serde_json::Value> {
48 LogIter::new_typed(inner)
49 }
50
51 pub fn new_typed<R>(inner: T) -> LogIter<T, R>
53 where
54 R: DeserializeOwned,
55 {
56 LogIter {
57 inner,
58 _p: PhantomData,
59 }
60 }
61}
62
63impl<T, R> From<T> for LogIter<T, R>
64where
65 T: Read,
66 R: DeserializeOwned,
67{
68 fn from(value: T) -> Self {
69 LogIter {
70 inner: value,
71 _p: PhantomData,
72 }
73 }
74}
75
76impl<T, R> Iterator for LogIter<T, R>
77where
78 T: Read,
79 R: DeserializeOwned,
80{
81 type Item = Result<R, LogIOError>;
82
83 fn next(&mut self) -> Option<Self::Item> {
84 let mut line = Vec::with_capacity(64); let mut buf = [0u8; 1];
87 loop {
88 let size = match self.inner.read(&mut buf) {
89 Ok(size) => size,
90 Err(e) => return Some(Err(e.into())),
91 };
92
93 if size == 0 {
94 break;
95 }
96
97 let byte = buf[0];
98
99 if byte == b'\n' && !line.is_empty() {
100 break;
101 }
102
103 if byte == 0x00 || (line.is_empty() && byte == b' ') {
104 continue;
105 }
106
107 line.push(byte);
108 }
109
110 if line.is_empty() {
111 return None;
112 }
113
114 Some(Ok(match serde_json::from_slice(&line) {
115 Ok(event) => event,
116 Err(e) => {
117 #[cfg(test)]
118 dbg!(String::from_utf8_lossy(&line).to_string());
119
120 return Some(Err(e.into()));
121 }
122 }))
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use crate::logs::LogEventContentKind;
129 use crate::modules::io::models::log_iter::LogIter;
130 use std::fs;
131 use std::fs::File;
132 use std::io::{BufReader, Cursor};
133
134 #[test]
135 fn log_reader_reads_completed_file_correctly() {
136 let data = r#"{ "timestamp":"2020-09-21T19:04:44Z", "event":"Repair", "Item":"Paint", "Cost":1 }
137{ "timestamp":"2020-09-21T19:04:51Z", "event":"Repair", "Item":"Wear", "Cost":10 }"#;
138
139 let cursor = Cursor::new(data);
140 let mut reader = LogIter::new(cursor);
141
142 assert!(reader.next().is_some());
143 assert!(reader.next().is_some());
144
145 assert!(dbg!(reader.next()).is_none());
146 }
147
148 #[test]
149 fn log_reader_handles_trailing_newlines_correctly() {
150 let data = r#"{ "timestamp":"2020-09-21T19:04:44Z", "event":"Repair", "Item":"Paint", "Cost":1 }
151{ "timestamp":"2020-09-21T19:04:51Z", "event":"Repair", "Item":"Wear", "Cost":10 }
152"#;
153
154 let cursor = Cursor::new(data);
155 let mut reader = LogIter::new(cursor);
156
157 assert!(reader.next().is_some());
158 assert!(reader.next().is_some());
159
160 assert!(dbg!(reader.next()).is_none());
161 }
162
163 #[test]
164 fn last_lines_are_read_correctly() {
165 fs::write("c.tmp", "").unwrap();
166 let file = File::open("c.tmp").unwrap();
167 let buf_reader = BufReader::new(file);
168
169 let mut reader = LogIter::new(buf_reader);
170
171 assert!(reader.next().is_none());
172
173 fs::write(
174 "c.tmp",
175 r#"{"timestamp":"2022-10-22T15:10:41Z","event":"Fileheader","part":1,"language":"English/UK","Odyssey":true,"gameversion":"4.0.0.1450","build":"r286858/r0 "}"#,
176 )
177 .unwrap();
178
179 assert_eq!(
180 reader.next().unwrap().unwrap().content.kind(),
181 LogEventContentKind::FileHeader
182 );
183
184 fs::write("c.tmp", r#"{"timestamp":"2022-10-22T15:10:41Z","event":"Fileheader","part":1,"language":"English/UK","Odyssey":true,"gameversion":"4.0.0.1450","build":"r286858/r0 "}
185{"timestamp":"2022-10-22T15:12:05Z","event":"Commander","FID":"F123456789","Name":"TEST"}"#)
186 .unwrap();
187
188 assert_eq!(
189 reader.next().unwrap().unwrap().content.kind(),
190 LogEventContentKind::Commander
191 );
192
193 fs::remove_file("c.tmp").unwrap();
194 }
195}