use std::io;
use std::path::Path;
use std::string::FromUtf8Error;
use thiserror::Error;
use crate::logs::content::LogEvent;
use super::RawLogFileReader;
#[derive(Debug)]
pub struct LogFileReader {
inner: RawLogFileReader,
}
#[derive(Debug, Error)]
pub enum LogFileReaderError {
#[error(transparent)]
IO(#[from] io::Error),
#[error(transparent)]
Utf8Error(#[from] FromUtf8Error),
#[error("Failed to parse log line: {0}")]
FailedToParseLine(#[from] serde_json::Error),
}
impl LogFileReader {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, LogFileReaderError> {
Ok(LogFileReader {
inner: RawLogFileReader::open(path)?,
})
}
}
impl Iterator for LogFileReader {
type Item = Result<LogEvent, LogFileReaderError>;
fn next(&mut self) -> Option<Self::Item> {
let result = match self.inner.next()? {
Ok(x) => x,
Err(e) => return Some(Err(e)),
};
Some(serde_json::from_value(result).map_err(|e| e.into()))
}
}
#[cfg(test)]
mod tests {
use std::fs;
use crate::logs::blocking::LogFileReader;
use crate::logs::content::log_event_content::fss_signal_discovered_event::FSSSignalDiscoveredEvent;
use crate::logs::content::log_event_content::LogEventContentKind;
use crate::logs::content::LogEventContent;
#[test]
fn partial_last_lines_are_read_correctly() {
fs::write("a.tmp", "").unwrap();
let mut reader = LogFileReader::open("a.tmp").unwrap();
assert!(reader.next().is_none());
fs::write(
"a.tmp",
r#"{"timestamp":"2022-10-22T15:10:41Z","event":"Fileheader","part":1,"#,
)
.unwrap();
assert!(reader.next().is_none());
fs::write("a.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::FileHeader
);
assert_eq!(
reader.next().unwrap().unwrap().content.kind(),
LogEventContentKind::Commander
);
assert!(reader.next().is_none());
fs::remove_file("a.tmp").unwrap();
}
#[test]
fn partial_last_lines_are_read_correctly_2() {
fs::write("d.tmp", "").unwrap();
let mut reader = LogFileReader::open("d.tmp").unwrap();
assert!(reader.next().is_none());
fs::write(
"d.tmp",
r#"{"timestamp":"2022-10-22T15:10:41Z","event":"Fileheader","part":1,"#,
)
.unwrap();
assert!(reader.next().is_none());
fs::write("d.tmp", r#"{"timestamp":"2022-10-22T15:10:41Z","event":"Fileheader","part":1,"language":"English/UK","#)
.unwrap();
assert!(reader.next().is_none());
fs::write("d.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
);
assert!(reader.next().is_none());
fs::remove_file("d.tmp").unwrap();
}
#[test]
fn partial_last_lines_are_read_correctly_3() {
fs::write("e.tmp", "").unwrap();
let mut reader = LogFileReader::open("e.tmp").unwrap();
assert!(reader.next().is_none());
fs::write(
"e.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!(reader.next().is_some());
fs::write("e.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",
"#)
.unwrap();
assert!(reader.next().is_none());
assert_eq!(
reader.inner.file_read_buffer,
r#"{"timestamp":"2022-10-22T15:12:05Z","#
);
fs::write("e.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
);
assert!(reader.next().is_none());
fs::remove_file("e.tmp").unwrap();
}
#[test]
fn incorrect_lines_return_an_err_only_when_it_is_expected() {
fs::write("b.tmp", "").unwrap();
let mut reader = LogFileReader::open("b.tmp").unwrap();
assert!(reader.next().is_none());
fs::write(
"b.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","Na BADLY FORMATTED
{"timestamp":"2022-10-22T15:12:33Z","event":"FSSSignalDiscovered","SystemAddress":5031654888146,"SignalName":"HMS CHUCKLE PHUCK J6K-8XT","IsStation":true}
{"timestamp":"2022-10-22T15:12:33Z","event":"FSSSignalDiscovered","SystemAddress":5031654888146,"#,
)
.unwrap();
assert_eq!(
reader.next().unwrap().unwrap().content.kind(),
LogEventContentKind::FileHeader
);
assert!(reader.next().unwrap().is_err());
assert_eq!(
reader.next().unwrap().unwrap().content,
LogEventContent::FSSSignalDiscovered(FSSSignalDiscoveredEvent {
system_address: 5031654888146,
signal_name: "HMS CHUCKLE PHUCK J6K-8XT".to_string(),
signal_name_localized: None,
is_station: true,
}),
);
assert!(reader.next().is_none());
fs::write(
"b.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","Na BADLY FORMATTED
{"timestamp":"2022-10-22T15:12:33Z","event":"FSSSignalDiscovered","SystemAddress":5031654888146,"SignalName":"HMS CHUCKLE PHUCK J6K-8XT","IsStation":true}
{"timestamp":"2022-10-22T15:12:33Z","event":"FSSSignalDiscovered","SystemAddress":5031654888146,"SignalName":"BREAK OF DAWN V3T-G0Y","IsStation":true}"#,
)
.unwrap();
assert_eq!(
reader.next().unwrap().unwrap().content,
LogEventContent::FSSSignalDiscovered(FSSSignalDiscoveredEvent {
system_address: 5031654888146,
signal_name: "BREAK OF DAWN V3T-G0Y".to_string(),
signal_name_localized: None,
is_station: true,
}),
);
fs::remove_file("b.tmp").unwrap();
}
#[test]
fn last_lines_are_read_correctly() {
fs::write("c.tmp", "").unwrap();
let mut reader = LogFileReader::open("c.tmp").unwrap();
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();
}
}