tramex_tools/interface/interface_file/
file_index.rs1use crate::errors::TramexError;
20use crate::interface::layer::Layer;
21use chrono::NaiveTime;
22use std::str::FromStr;
23
24#[derive(Debug, Clone)]
26pub struct LogMetadata {
27 pub start_line: usize,
29
30 pub end_line: usize,
32
33 pub timestamp: i64,
35
36 pub layer: Layer,
38}
39
40#[derive(Debug, Clone)]
42pub struct FileIndex {
43 pub log_metadata: Vec<LogMetadata>,
45
46 pub total_count: usize,
48}
49
50impl FileIndex {
51 pub fn build_from_lines(lines: &[String]) -> Result<Self, TramexError> {
58 let estimated_logs = lines.len() / 10;
60 let mut log_metadata = Vec::with_capacity(estimated_logs);
61 let mut current_start: Option<usize> = None;
62
63 for (line_idx, line) in lines.iter().enumerate() {
64 let first_byte = line.as_bytes().first();
66
67 match first_byte {
68 Some(b'#') => continue, Some(b' ') | Some(b'\t') => continue, Some(_) if line.is_empty() => continue, None => continue, _ => {}
73 }
74
75 if let Some(start) = current_start {
77 if let Some(metadata) = Self::extract_metadata_fast(lines, start, line_idx) {
79 log_metadata.push(metadata);
80 }
81 }
82
83 current_start = Some(line_idx);
85 }
86
87 if let Some(start) = current_start
89 && let Some(metadata) = Self::extract_metadata_fast(lines, start, lines.len())
90 {
91 log_metadata.push(metadata);
92 }
93
94 let total_count = log_metadata.len();
95 log::info!("Index built: {} logs from {} lines", total_count, lines.len());
96
97 Ok(Self {
98 log_metadata,
99 total_count,
100 })
101 }
102
103 fn extract_metadata_fast(lines: &[String], start_line: usize, end_line: usize) -> Option<LogMetadata> {
105 let first_line = lines.get(start_line)?;
106
107 let mut parts = first_line.split_whitespace();
109 let time_str = parts.next()?;
110 let layer_str = parts.next()?;
111
112 let time = NaiveTime::parse_from_str(time_str, "%H:%M:%S%.3f").ok()?;
114 let timestamp = super::super::parser::time_to_milliseconds(&time);
115
116 let layer_clean = layer_str.trim_start_matches('[').trim_end_matches(']');
118 let layer = Layer::from_str(layer_clean).ok()?;
119
120 Some(LogMetadata {
121 start_line,
122 end_line,
123 timestamp,
124 layer,
125 })
126 }
127
128 #[allow(dead_code)]
130 fn extract_metadata(lines: &[String], start_line: usize, end_line: usize) -> Option<LogMetadata> {
131 if start_line >= lines.len() {
132 return None;
133 }
134
135 let first_line = &lines[start_line];
136 let parts: Vec<&str> = first_line.split_whitespace().collect();
137
138 if parts.len() < 2 {
139 return None;
140 }
141
142 let timestamp = match NaiveTime::parse_from_str(parts[0], "%H:%M:%S%.3f") {
144 Ok(time) => super::super::parser::time_to_milliseconds(&time),
145 Err(_) => return None,
146 };
147
148 let layer_str = parts[1].trim_start_matches('[').trim_end_matches(']');
150 let layer = match Layer::from_str(layer_str) {
151 Ok(l) => l,
152 Err(_) => return None,
153 };
154
155 Some(LogMetadata {
156 start_line,
157 end_line,
158 timestamp,
159 layer,
160 })
161 }
162
163 pub fn find_next_enabled_index(&self, current_index: usize, layer_filter: &impl Fn(&Layer) -> bool) -> Option<usize> {
165 for idx in (current_index + 1)..self.log_metadata.len() {
166 if let Some(metadata) = self.log_metadata.get(idx)
167 && layer_filter(&metadata.layer)
168 {
169 return Some(idx);
170 }
171 }
172 None
173 }
174
175 pub fn find_previous_enabled_index(
177 &self,
178 current_index: usize,
179 layer_filter: &impl Fn(&Layer) -> bool,
180 ) -> Option<usize> {
181 if current_index == 0 {
182 return None;
183 }
184
185 for idx in (0..current_index).rev() {
186 if let Some(metadata) = self.log_metadata.get(idx)
187 && layer_filter(&metadata.layer)
188 {
189 return Some(idx);
190 }
191 }
192 None
193 }
194
195 pub fn get_metadata(&self, index: usize) -> Option<&LogMetadata> {
197 self.log_metadata.get(index)
198 }
199}