diff --git a/Cargo.lock b/Cargo.lock index edb11d6..8cf1e22 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1027,7 +1027,6 @@ dependencies = [ "main_error", "matchit", "osc94", - "rar-stream", "ratatui", "rayon", "regex", @@ -1040,6 +1039,7 @@ dependencies = [ "tikv-jemallocator", "time", "tinystr", + "unrar", "xz2", "zip", ] @@ -1438,16 +1438,6 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -[[package]] -name = "rar-stream" -version = "5.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bad41fc4447843b83b5e858b778dedcbbaf116e2bd3d7e21ab19ed096a942ef" -dependencies = [ - "crossbeam-channel", - "rayon", -] - [[package]] name = "ratatui" version = "0.30.0" @@ -2123,6 +2113,29 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" +[[package]] +name = "unrar" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ec61343a630d2b50d13216dea5125e157d3fc180a7d3f447d22fe146b648fc" +dependencies = [ + "bitflags 2.11.0", + "regex", + "unrar_sys", + "widestring", +] + +[[package]] +name = "unrar_sys" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b77675b883cfbe6bf41e6b7a5cd6008e0a83ba497de3d96e41a064bbeead765" +dependencies = [ + "cc", + "libc", + "winapi", +] + [[package]] name = "utf8parse" version = "0.2.2" @@ -2347,6 +2360,12 @@ dependencies = [ "wezterm-dynamic", ] +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 60c0c15..bbf83d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,6 @@ xz2 = "0.1.7" bzip2-rs = "0.1.2" ruzstd = "0.8.2" sevenz-rust2 = "0.20.2" -rar-stream = "5.7.1" dialoguer = "0.12.0" indicatif = { version = "0.18.3", features = ["rayon"] } csv = "1.4.0" @@ -40,6 +39,8 @@ matchit = "0.9.0" [target.'cfg(target_env = "musl")'.dependencies] tikv-jemallocator = "0.6.1" +[target.'cfg(all(unix, not(target_env = "musl")))'.dependencies] +unrar = "0.5.8" [profile.dev.package."*"] opt-level = 3 diff --git a/data/src/types.rs b/data/src/types.rs index 6bc49fd..93f9f28 100644 --- a/data/src/types.rs +++ b/data/src/types.rs @@ -10,7 +10,7 @@ pub enum LogLevel { Info = 1, Warn = 2, Error = 3, - Exception = 4, + Exception, #[default] Unknown, } diff --git a/src/app.rs b/src/app.rs index 69e7d9b..934a910 100644 --- a/src/app.rs +++ b/src/app.rs @@ -2,7 +2,6 @@ use crate::grouping::{GroupingResult, UniqueGrouping}; use crate::logfile::{LogFile, LogLine, LogLineNumber}; use crate::logs::ParsedLogs; use crate::timegraph::{SparkLine, TimeGraph}; -use logsmash_data::LogLevel; use regex::{escape, Regex, RegexBuilder}; use serde_json::Error as JsonError; use std::cell::OnceCell; @@ -84,10 +83,10 @@ impl<'logs> LineSet<'logs> { } pub fn matches(&self, filter: &Filter) -> bool { - let line = self.lines[0]; if filter.is_empty() { return true; } + let line = self.lines[0]; if line.request_id == filter.filter { return true; } @@ -99,23 +98,15 @@ impl<'logs> LineSet<'logs> { } } -#[derive(Clone)] +#[derive(Default, Clone)] pub struct Filter { filter: String, regexes: Vec, - pub levels: [bool; 6], -} - -impl Default for Filter { - fn default() -> Self { - EMPTY_FILTER.clone() - } } pub static EMPTY_FILTER: Filter = Filter { filter: String::new(), regexes: Vec::new(), - levels: [true; 6], }; impl Filter { @@ -131,24 +122,18 @@ impl Filter { .collect() } - pub fn level(&self, level: LogLevel) -> bool { - self.levels[level as i64 as usize] - } - - pub fn toggle_level(&mut self, level: LogLevel) { - self.levels[level as i64 as usize] = !self.levels[level as i64 as usize]; + #[allow(dead_code)] + pub fn new(filter: String) -> Self { + let regexes = Self::build_regex(&filter); + Filter { filter, regexes } } pub fn parts(&self) -> impl Iterator { self.regexes.iter() } - pub fn all_levels(&self) -> bool { - self.levels.iter().all(|level| *level) - } - pub fn is_empty(&self) -> bool { - self.filter.is_empty() && self.levels.iter().all(|level| *level) + self.filter.is_empty() } pub fn push(&mut self, c: char) { diff --git a/src/error.rs b/src/error.rs index 66b0b2c..99a4c58 100644 --- a/src/error.rs +++ b/src/error.rs @@ -27,8 +27,9 @@ pub enum ReadError { Zstd(#[from] FrameDecoderError), #[error(transparent)] SevenZip(#[from] sevenz_rust2::Error), + #[cfg(all(unix, not(target_env = "musl")))] #[error(transparent)] - Rar(#[from] rar_stream::error::RarError), + Rar(#[from] unrar::error::UnrarError), #[error("archive contains no files")] NoFiles, #[error("log file contained non-utf8 characters: {0:#}")] diff --git a/src/grouping/mod.rs b/src/grouping/mod.rs index d861bd2..38e4e00 100644 --- a/src/grouping/mod.rs +++ b/src/grouping/mod.rs @@ -95,9 +95,6 @@ impl<'logs> LogGrouping<'logs> { if filter.is_empty() { return true; } - if !filter.all_levels() && !self.lines.lines.iter().any(|line| filter.level(line.level)) { - return false; - } match &self.result { Some(result) => result.matches(filter), _ => true, diff --git a/src/grouping/unique.rs b/src/grouping/unique.rs index f9e1532..8f58b74 100644 --- a/src/grouping/unique.rs +++ b/src/grouping/unique.rs @@ -28,9 +28,6 @@ impl<'a> GroupingResult<'a> for UniqueGrouping<'a> { if filter.is_empty() { return true; } - if !filter.level(self.line.level) { - return false; - } filter.parts().all(|filter_part| { if let Some(ex) = self.line.exception.as_ref() { if filter_part.is_match(&ex.message) || filter_part.is_match(&ex.exception) { diff --git a/src/logfile/archive/rar.rs b/src/logfile/archive/rar.rs index c2815a2..0497c03 100644 --- a/src/logfile/archive/rar.rs +++ b/src/logfile/archive/rar.rs @@ -1,50 +1,51 @@ use crate::error::ReadError; use crate::logfile::archive::{Archive, ArchiveEntry}; -use rar_stream::MemoryArchive; use std::borrow::Cow; -use std::fs::read; pub struct RarArchive { - archive: MemoryArchive, + path: String, } impl RarArchive { pub fn new(path: &str) -> Result { - let data = read(path)?; - Ok(RarArchive { - archive: MemoryArchive::from_vec(data)?, - }) + Ok(RarArchive { path: path.into() }) } } -pub struct RarEntry<'a> { - archive: &'a MemoryArchive, - index: usize, +pub struct RarEntry { + archive: String, name: String, } -impl ArchiveEntry for RarEntry<'_> { +impl ArchiveEntry for RarEntry { fn name<'a>(&'a self) -> Cow<'a, str> { self.name.as_str().into() } fn extract(self) -> Result, ReadError> { - Ok(self.archive.extract(self.index)?) + let mut archive = unrar::Archive::new(&self.archive).open_for_processing()?; + while let Some(header) = archive.read_header()? { + if header.entry().filename.to_string_lossy() == self.name { + return Ok(header.read()?.0); + } + archive = header.skip()?; + } + Err(ReadError::NoFiles) } } impl Archive for RarArchive { - type Entry<'a> = RarEntry<'a>; + type Entry<'a> = RarEntry; fn entries(&mut self) -> impl Iterator> { - self.archive - .entries_iter() - .enumerate() - .filter(|(_, entry)| !entry.is_directory) - .map(|(index, entry)| RarEntry { - archive: &self.archive, - index, - name: entry.name, + unrar::Archive::new(&self.path) + .open_for_listing() + .into_iter() + .flatten() + .flatten() + .map(|header| RarEntry { + archive: self.path.clone(), + name: header.filename.to_string_lossy().into(), }) } } diff --git a/src/ui/footer.rs b/src/ui/footer.rs index 951e9ef..058fc0c 100644 --- a/src/ui/footer.rs +++ b/src/ui/footer.rs @@ -55,13 +55,13 @@ pub fn footer<'a>(app: &App, params: FooterParams<'a>) -> Table<'a> { fn help(page: UiPage) -> &'static str { match page { - UiPage::GroupList => "«Q» Exit - «Enter» Select - «F» Filter - «L» Toggle Level - «E» Show parse errors", - UiPage::Group => "«Q» Exit - «Enter» Select - «F» Filter - «L» Toggle Level - «G» Group By - «Esc» Back", + UiPage::GroupList => "«Q» Exit - «Enter» Select - «F» Filter - «E» Show parse errors", + UiPage::Group => "«Q» Exit - «Enter» Select - «F» Filter - «G» Group By - «Esc» Back", UiPage::DistinctLogs => { - "«Q» Exit - «F» Filter - «L» Toggle Level - «Esc» Back - «C» Copy log line - «Shift» + «C» Copy all lines - «G» Group By - «R» Show logs for request" + "«Q» Exit - «F» Filter - «Esc» Back - «C» Copy log line - «Shift» + «C» Copy all lines - «G» Group By - «R» Show logs for request" } UiPage::ByRequest => { - "«Q» Exit - «F» Filter - «L» Toggle Level - «Esc» Back - «C» Copy log line - «Shift» + «C» Copy all lines" + "«Q» Exit - «F» Filter - «Esc» Back - «C» Copy log line - «Shift» + «C» Copy all lines" } UiPage::Log => { "«Q» Exit - «Esc» Back - «R» Toggle raw - «C» Copy log line - «R» Show logs for request" diff --git a/src/ui/input.rs b/src/ui/input.rs index 9d207a2..a186134 100644 --- a/src/ui/input.rs +++ b/src/ui/input.rs @@ -19,12 +19,11 @@ pub enum UiEvent { Copy, CopyAll, EnterFilterMode, + ClearFilter, Text(char), PopText(PopMode), ByRequest, GroupBy, - ToggleLevelFilterMode, - Toggle, } pub enum PopMode { @@ -62,7 +61,7 @@ pub fn handle_events(page: UiPage, ui_state: &UiState) -> io::Result Some(UiEvent::Back), + (Mode::FilterInput, KeyCode::Esc) => Some(UiEvent::ClearFilter), (Mode::FilterInput, KeyCode::F(4)) => Some(UiEvent::Back), (Mode::FilterInput, KeyCode::Backspace) => { Some(UiEvent::PopText(PopMode::Character)) @@ -73,9 +72,6 @@ pub fn handle_events(page: UiPage, ui_state: &UiState) -> io::Result Some(UiEvent::Text(c)), - - (_, KeyCode::Char(' ')) => Some(UiEvent::Toggle), - (_, KeyCode::Char('l')) => Some(UiEvent::ToggleLevelFilterMode), _ => None, }); } diff --git a/src/ui/list.rs b/src/ui/list.rs index e06ce0e..d257520 100644 --- a/src/ui/list.rs +++ b/src/ui/list.rs @@ -3,7 +3,6 @@ use ratatui::buffer::Buffer; use ratatui::layout::Rect; use ratatui::prelude::StatefulWidget; use ratatui::widgets::{Block, HighlightSpacing, List, ListItem, ListState}; -use std::fmt::Display; pub struct SelectList<'a> { list: List<'a>, @@ -38,51 +37,6 @@ impl StatefulWidget for SelectList<'_> { } } -pub struct ToggleList<'a> { - list: List<'a>, -} - -impl<'a> ToggleList<'a> { - pub fn new(items: T) -> Self - where - T: IntoIterator, - S: Display, - { - fn symbol(state: bool) -> &'static str { - if state { - "☑" - } else { - "☐" - } - } - - ToggleList { - list: List::new( - items - .into_iter() - .map(|(text, state)| format!("{} {text}", symbol(state))), - ) - .highlight_style(TABLE_SELECTED_STYLE) - .highlight_spacing(HighlightSpacing::Always), - } - } - - #[must_use = "method moves the value of self and returns the modified value"] - pub fn block(self, block: Block<'a>) -> Self { - ToggleList { - list: self.list.block(block), - } - } -} - -impl StatefulWidget for ToggleList<'_> { - type State = SizedListState; - - fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { - StatefulWidget::render(self.list, area, buf, &mut state.list_state); - } -} - #[derive(PartialEq)] pub struct SizedListState { list_state: ListState, diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 0b30944..a8af5e0 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -7,12 +7,12 @@ use crate::ui::footer::footer; use crate::ui::grouping_list::grouping_list; use crate::ui::histogram::UiHistogram; use crate::ui::input::handle_events; -use crate::ui::list::{SelectList, ToggleList}; +use crate::ui::list::SelectList; use crate::ui::single_group::single_group; use crate::ui::single_log::single_log; use crate::ui::state::{ DistinctLogsState, ErrorLinesState, ErrorState, GroupByMenuState, GroupListState, GroupState, - LevelsMenuState, LogState, UiState, + LogState, UiState, }; use ratatui::crossterm::event::{DisableMouseCapture, EnableMouseCapture}; use ratatui::crossterm::terminal::{ @@ -189,32 +189,6 @@ fn ui(frame: &mut Frame, app: &App, state: &mut UiState) { frame.render_widget(Clear, area); frame.render_stateful_widget(popup, area, list_state); } - UiState::LevelsMenu(LevelsMenuState { - previous, - list_state, - levels, - .. - }) => { - ui(frame, app, previous); - let options = [ - ("Debug", levels[0]), - ("Info", levels[1]), - ("Warn", levels[2]), - ("Error", levels[3]), - ("Exception", levels[4]), - ("Unknown", levels[5]), - ]; - let area = center( - frame.area(), - Constraint::Percentage(20), - Constraint::Length(options.len() as u16 + 2), // top and bottom border + content - ); - let block = Block::bordered().title("Levels"); - - let popup = ToggleList::new(options).block(block); - frame.render_widget(Clear, area); - frame.render_stateful_widget(popup, area, list_state); - } UiState::Distinct(DistinctLogsState { lines, table_state, diff --git a/src/ui/state.rs b/src/ui/state.rs index 9a212f9..ad47666 100644 --- a/src/ui/state.rs +++ b/src/ui/state.rs @@ -19,7 +19,6 @@ pub enum UiState<'a> { GroupList(GroupListState<'a>), Group(GroupState<'a>), GroupByMenu(GroupByMenuState<'a>), - LevelsMenu(LevelsMenuState<'a>), Distinct(DistinctLogsState<'a>), Log(LogState<'a>), Errors(ErrorLinesState<'a>), @@ -37,7 +36,6 @@ impl<'a> UiState<'a> { .. }) | UiState::GroupByMenu(_) - | UiState::LevelsMenu(_) | UiState::Distinct(DistinctLogsState { .. }) | UiState::Log(LogState { .. }) | UiState::Errors(ErrorLinesState { .. }) @@ -215,24 +213,6 @@ impl<'a> GroupState<'a> { } } -pub struct LevelsMenuState<'a> { - pub previous: Box>, - pub list_state: SizedListState, - pub levels: [bool; 6], -} - -impl PartialEq for LevelsMenuState<'_> { - fn eq(&self, other: &Self) -> bool { - self.list_state == other.list_state - } -} - -impl<'a> LevelsMenuState<'a> { - fn selected(&self) -> usize { - self.list_state.selected() - } -} - impl PartialEq for GroupState<'_> { fn eq(&self, other: &Self) -> bool { self.result.result == other.result.result @@ -399,7 +379,6 @@ impl<'a> UiState<'a> { UiState::Quit | UiState::GroupList(_) => UiPage::GroupList, UiState::Group(_) => UiPage::Group, UiState::GroupByMenu(_) => UiPage::Group, // todo - UiState::LevelsMenu(_) => UiPage::Group, // todo UiState::Distinct(DistinctLogsState { grouping: DistinctLogGrouping::Message, .. @@ -476,7 +455,6 @@ impl<'a> UiState<'a> { fn list_state_mut(&mut self) -> Option<&mut SizedListState> { match self { UiState::GroupByMenu(state) => Some(&mut state.list_state), - UiState::LevelsMenu(state) => Some(&mut state.list_state), _ => None, } } @@ -533,7 +511,6 @@ impl<'a> UiState<'a> { UiState::Group(_) => UI_HEADER_SIZE + 1, UiState::Distinct(_) => UI_HEADER_SIZE + 1, UiState::GroupByMenu(_) => 0, - UiState::LevelsMenu(_) => 0, UiState::Log(_) => 0, UiState::Errors(_) => 0, UiState::Error(_) => 0, @@ -625,6 +602,9 @@ impl<'a> UiState<'a> { state.filter.pop(); (true, state.into()) } + (UiState::GroupByMenu(GroupByMenuState { previous, .. }), UiEvent::ClearFilter) => { + (true, *previous) + } (UiState::Distinct(state), UiEvent::Select) => { let selected = state.selected(); (true, state.enter(selected, app)) @@ -677,7 +657,7 @@ impl<'a> UiState<'a> { copy_osc(raw); (false, UiState::Errors(state)) } - (mut ui, UiEvent::EnterFilterMode) if ui.mode() == Mode::Normal => { + (mut ui, UiEvent::EnterFilterMode) if ui.mode() != Mode::FilterInput => { ui.set_mode(Mode::FilterInput); (true, ui) } @@ -696,34 +676,12 @@ impl<'a> UiState<'a> { } (true, ui) } - ( - ui @ (UiState::GroupList(_) | UiState::Group(_) | UiState::Distinct(_)), - UiEvent::ToggleLevelFilterMode, - ) if ui.mode() == Mode::Normal => { - let levels = ui.filter().map(|filter| filter.levels).unwrap_or([true; 6]); - let state = LevelsMenuState { - previous: Box::new(ui), - list_state: SizedListState::new(levels.len()), - levels, - }; - (true, UiState::LevelsMenu(state)) - } - (UiState::LevelsMenu(mut state), UiEvent::Toggle) => { - state.levels[state.selected()] = !state.levels[state.selected()]; - (true, UiState::LevelsMenu(state)) - } - ( - UiState::LevelsMenu(LevelsMenuState { - mut previous, - levels, - .. - }), - UiEvent::Back | UiEvent::ToggleLevelFilterMode | UiEvent::Select, - ) => { - if let Some(filter) = previous.filter_mut() { - filter.levels = levels; + (mut ui, UiEvent::ClearFilter) if ui.mode() != Mode::Normal => { + if let Some(filter) = ui.filter_mut() { + filter.clear(); } - (true, *previous) + ui.set_mode(Mode::Normal); + (true, ui) } ( mut ui @ UiState::GroupList(GroupListState {