145 lines
3.7 KiB
Rust
145 lines
3.7 KiB
Rust
use std::{
|
|
cell::RefCell,
|
|
fs::File,
|
|
io::{self, BufRead, BufReader},
|
|
mem,
|
|
path::{Path, PathBuf},
|
|
rc::Rc,
|
|
sync::OnceLock,
|
|
};
|
|
|
|
use crate::tui::{
|
|
model::{LogEntry, RawLogEntry},
|
|
processing::LogStream,
|
|
};
|
|
|
|
struct Inner {
|
|
pub path: PathBuf,
|
|
|
|
file: BufReader<File>,
|
|
cached_entries: Vec<Rc<LogEntry>>,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct LogfileReader(Rc<RefCell<Inner>>);
|
|
|
|
impl LogfileReader {
|
|
pub fn new(p: &Path) -> io::Result<Self> {
|
|
Ok(Self(Rc::new(RefCell::new(Inner {
|
|
file: BufReader::new(File::open(p)?),
|
|
path: p.to_path_buf(),
|
|
cached_entries: Vec::new(),
|
|
}))))
|
|
}
|
|
|
|
pub fn path(&self) -> PathBuf {
|
|
self.0.borrow().path.clone()
|
|
}
|
|
|
|
fn next_line(&mut self) -> Option<String> {
|
|
let mut res = String::new();
|
|
match self.0.borrow_mut().file.read_line(&mut res) {
|
|
Err(e) => {
|
|
eprintln!("error: {e:?}");
|
|
None
|
|
}
|
|
Ok(0) => None,
|
|
Ok(_) => Some(res),
|
|
}
|
|
}
|
|
|
|
fn next_raw_entry(&mut self) -> Option<RawLogEntry> {
|
|
let line = self.next_line()?;
|
|
match serde_json::from_str(&line) {
|
|
Ok(i) => Some(i),
|
|
Err(e) => {
|
|
eprintln!("deserializing: {e:?} in {line}");
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
fn next_entry(&mut self) -> Option<Rc<LogEntry>> {
|
|
let mut stack = Vec::<(RawLogEntry, Vec<Rc<LogEntry>>)>::new();
|
|
let mut curr = Vec::<Rc<LogEntry>>::new();
|
|
|
|
loop {
|
|
let entry = self.next_raw_entry()?;
|
|
|
|
let new_entry = Rc::new(match entry.fields.message() {
|
|
Some("enter") => {
|
|
stack.push((entry, mem::take(&mut curr)));
|
|
continue;
|
|
}
|
|
Some("exit") => {
|
|
// TODO: does it match?
|
|
let Some((enter, prev)) = stack.pop() else {
|
|
panic!("exit before entry");
|
|
};
|
|
let sub_entries = mem::replace(&mut curr, prev);
|
|
LogEntry::Sub {
|
|
enter: enter,
|
|
sub_entries,
|
|
exit: entry,
|
|
count_sub: OnceLock::new(),
|
|
}
|
|
}
|
|
_ => LogEntry::Single { raw: entry },
|
|
});
|
|
|
|
if stack.is_empty() {
|
|
return Some(new_entry);
|
|
} else {
|
|
curr.push(new_entry);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn fill_buf_to_access_index(&mut self, n: usize) -> Option<Rc<LogEntry>> {
|
|
while self.0.borrow().cached_entries.len() <= n {
|
|
let entry = self.next_entry()?;
|
|
self.0.borrow_mut().cached_entries.push(entry);
|
|
}
|
|
|
|
Some(Rc::clone(&self.0.borrow().cached_entries[n]))
|
|
}
|
|
|
|
pub fn iter(&self) -> LogFileReaderStream {
|
|
LogFileReaderStream {
|
|
reader: self.clone(),
|
|
curr: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct LogFileReaderStream {
|
|
reader: LogfileReader,
|
|
curr: usize,
|
|
}
|
|
|
|
impl LogStream for LogFileReaderStream {
|
|
fn next(&mut self) -> Option<(Rc<LogEntry>, usize)> {
|
|
let entry = self.reader.fill_buf_to_access_index(self.curr)?;
|
|
self.curr += 1;
|
|
|
|
Some((entry, 0))
|
|
}
|
|
|
|
fn prev(&mut self) -> Option<(Rc<LogEntry>, usize)> {
|
|
if self.curr == 0 {
|
|
return None;
|
|
}
|
|
|
|
let entry = self.reader.fill_buf_to_access_index(self.curr)?;
|
|
self.curr -= 1;
|
|
|
|
Some((entry, 0))
|
|
}
|
|
|
|
fn clone(&self) -> Box<dyn LogStream> {
|
|
Box::new(Self {
|
|
reader: self.reader.clone(),
|
|
curr: self.curr,
|
|
})
|
|
}
|
|
}
|