logviewer/src/tui/reader.rs
2026-02-24 22:04:41 +01:00

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,
})
}
}