use crate::logs::LogEvent;
use crate::modules::io::error::LogError;
use std::io::Read;
#[derive(Debug)]
pub struct LogIter<T>
where
T: Read,
{
inner: T,
}
impl<T> From<T> for LogIter<T>
where
T: Read,
{
fn from(value: T) -> Self {
LogIter { inner: value }
}
}
impl<T> Iterator for LogIter<T>
where
T: Read,
{
type Item = Result<LogEvent, LogError>;
fn next(&mut self) -> Option<Self::Item> {
let mut line = Vec::with_capacity(64);
let mut buf = [0u8; 1];
loop {
let size = match self.inner.read(&mut buf) {
Ok(size) => size,
Err(e) => return Some(Err(e.into())),
};
if size == 0 {
break;
}
let byte = buf[0];
if byte == b'\n' && !line.is_empty() {
break;
}
if byte == 0x00 || (line.is_empty() && byte == b' ') {
continue;
}
line.push(byte);
}
if line.is_empty() {
return None;
}
Some(Ok(match serde_json::from_slice(&line) {
Ok(event) => event,
Err(e) => {
#[cfg(test)]
dbg!(&line);
return Some(Err(e.into()));
}
}))
}
}
#[cfg(test)]
mod tests {
use crate::logs::LogEventContentKind;
use crate::modules::io::models::log_file::log_iter::LogIter;
use std::fs;
use std::fs::File;
use std::io::{BufReader, Cursor};
#[test]
fn log_reader_reads_completed_file_correctly() {
let data = r#"{ "timestamp":"2020-09-21T19:04:44Z", "event":"Repair", "Item":"Paint", "Cost":1 }
{ "timestamp":"2020-09-21T19:04:51Z", "event":"Repair", "Item":"Wear", "Cost":10 }"#;
let cursor = Cursor::new(data);
let mut reader = LogIter::from(cursor);
assert!(reader.next().is_some());
assert!(reader.next().is_some());
assert!(dbg!(reader.next()).is_none());
}
#[test]
fn log_reader_handles_trailing_newlines_correctly() {
let data = r#"{ "timestamp":"2020-09-21T19:04:44Z", "event":"Repair", "Item":"Paint", "Cost":1 }
{ "timestamp":"2020-09-21T19:04:51Z", "event":"Repair", "Item":"Wear", "Cost":10 }
"#;
let cursor = Cursor::new(data);
let mut reader = LogIter::from(cursor);
assert!(reader.next().is_some());
assert!(reader.next().is_some());
assert!(dbg!(reader.next()).is_none());
}
#[test]
#[ignore]
fn last_lines_are_read_correctly() {
fs::write("c.tmp", "").unwrap();
let file = File::open("c.tmp").unwrap();
let buf_reader = BufReader::new(file);
let mut reader = LogIter::from(buf_reader);
assert!(reader.next().is_none());
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 "}"#,
)
.unwrap();
assert_eq!(
reader.next().unwrap().unwrap().content.kind(),
LogEventContentKind::FileHeader
);
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 "}
{"timestamp":"2022-10-22T15:12:05Z","event":"Commander","FID":"F123456789","Name":"TEST"}"#)
.unwrap();
assert_eq!(
reader.next().unwrap().unwrap().content.kind(),
LogEventContentKind::Commander
);
fs::remove_file("c.tmp").unwrap();
}
}