tramex_tools/interface/interface_file/
file_handler.rs

1//! File Handler
2
3use crate::data::{AdditionalInfos, Data, Trace};
4use crate::errors::ErrorCode;
5use crate::errors::TramexError;
6use crate::interface::interface_types::InterfaceTrait;
7use crate::interface::layer::Layers;
8use crate::interface::parse_config::{FileMetadata, Technology};
9use crate::tramex_error;
10use std::path::PathBuf;
11//use std::collections::HashMap;
12
13use super::file_index::FileIndex;
14use super::utils_file::parse_one_block;
15
16/// The default number of log processed by batch
17const BATCH_SIZE: usize = 100;
18
19#[derive(Debug, Clone)]
20/// Data structure to store the file.
21pub struct File {
22    /// Path of the file.
23    pub file_path: PathBuf,
24
25    /// Content of the file.
26    pub file_content: Vec<String>,
27
28    /// Full read status of the file.
29    pub full_read: bool,
30
31    /// the number of log to read each batch
32    nb_read: usize,
33
34    /// The previous line number (deprecated - kept for compatibility)
35    index_line: usize,
36
37    /// Available
38    pub available: bool,
39
40    /// File index for efficient navigation (Option 3)
41    pub index: Option<FileIndex>,
42
43    /// Cache of parsed traces (index -> Trace)
44    //_parsed_cache: HashMap<usize, Trace>,
45
46    /// Current logical index in the file index
47    pub current_log_index: usize,
48}
49
50impl Default for File {
51    fn default() -> Self {
52        Self {
53            file_path: PathBuf::from(""),
54            file_content: vec![],
55            full_read: false,
56            nb_read: BATCH_SIZE,
57            index_line: 0,
58            available: true,
59            index: None,
60            //parsed_cache: HashMap::new(),
61            current_log_index: 0,
62        }
63    }
64}
65
66impl InterfaceTrait for File {
67    fn get_more_data(&mut self, _layer_list: Layers, data: &mut Data) -> Result<(), Vec<TramexError>> {
68        if self.full_read {
69            return Ok(());
70        }
71
72        // Parse metadata on first call and build index
73        if self.index.is_none() && data.events.is_empty() {
74            data.metadata = FileMetadata::parse_from_lines(&self.file_content);
75
76            // Build file index (Option 3)
77            log::info!("Building file index...");
78            match FileIndex::build_from_lines(&self.file_content) {
79                Ok(index) => {
80                    log::info!("File index built: {} logs found", index.total_count);
81                    self.index = Some(index);
82                }
83                Err(e) => {
84                    log::error!("Failed to build file index: {}", e.message);
85                    return Err(vec![e]);
86                }
87            }
88        }
89
90        // Use old batch processing for now (will be optimized later)
91        let (mut traces, err_processed) = self.process();
92
93        // Infer technology from RRC canal name if metadata is Unknown
94        if data.metadata.technology == Technology::Unknown {
95            for trace in &traces {
96                if let AdditionalInfos::RRCInfos(infos) = &trace.additional_infos {
97                    if infos.canal.ends_with("-NR") {
98                        data.metadata.technology = Technology::NR;
99                    } else {
100                        data.metadata.technology = Technology::LTE;
101                    }
102                    break; // Only need to check the first RRC trace
103                }
104            }
105        }
106
107        data.events.append(&mut traces);
108        if !err_processed.is_empty() {
109            let filtered: Vec<TramexError> = err_processed
110                .iter()
111                .filter(|tmx_err| !matches!(tmx_err.get_code(), ErrorCode::EndOfFile))
112                .filter(|tmx_err| !matches!(tmx_err.get_code(), ErrorCode::ParsingLayerNotImplemented))
113                .cloned()
114                .collect();
115            // Only return Err if there are real errors remaining
116            if !filtered.is_empty() {
117                return Err(filtered);
118            }
119        }
120        Ok(())
121    }
122
123    fn close(&mut self) -> Result<(), TramexError> {
124        Ok(())
125    }
126
127    fn supports_preloading(&self) -> bool {
128        true
129    }
130
131    fn get_total_event_count(&self) -> Option<usize> {
132        self.index.as_ref().map(|idx| idx.total_count)
133    }
134
135    fn is_fully_read(&self) -> bool {
136        self.full_read
137    }
138}
139
140impl File {
141    /// Create a new file.
142    pub fn new(file_path: PathBuf, file_content: String) -> Self {
143        Self {
144            file_path,
145            file_content: file_content.lines().map(|x| x.to_string()).collect(),
146            full_read: false,
147            nb_read: BATCH_SIZE,
148            index_line: 0,
149            available: true,
150            index: None,
151            //parsed_cache: HashMap::new(),
152            current_log_index: 0,
153        }
154    }
155
156    /// set file mode using a path and content
157    pub fn new_file_content(file_path: PathBuf, file_content: String) -> Self {
158        Self {
159            file_path,
160            file_content: file_content.lines().map(|x| x.to_string()).collect(),
161            full_read: false,
162            nb_read: BATCH_SIZE,
163            index_line: 0,
164            available: true,
165            index: None,
166            //parsed_cache: HashMap::new(),
167            current_log_index: 0,
168        }
169    }
170
171    /// To update the number of log to read per batch
172    pub fn change_nb_read(&mut self, toread: usize) {
173        self.nb_read = toread;
174    }
175
176    /// To process the file and parse a batch of log
177    pub fn process(&mut self) -> (Vec<Trace>, Vec<TramexError>) {
178        let (vec_trace, opt_err) = File::process_string(&self.file_content, self.nb_read, &mut self.index_line);
179        for one_error in &opt_err {
180            if matches!(one_error.get_code(), ErrorCode::EndOfFile) {
181                self.full_read = true;
182            }
183        }
184        (vec_trace, opt_err)
185    }
186    /// To process a string passed in argument, with index and batch to read
187    pub fn process_string(lines: &[String], nb_to_read: usize, ix: &mut usize) -> (Vec<Trace>, Vec<TramexError>) {
188        let mut traces = vec![];
189        let mut errors = vec![];
190        for _ in 0..nb_to_read {
191            if *ix >= lines.len() {
192                errors.push(tramex_error!("End of file".to_string(), ErrorCode::EndOfFile));
193                break;
194            }
195            match parse_one_block(&lines[*ix..], ix) {
196                Ok(trace) => {
197                    traces.push(trace);
198                }
199                Err(err) => {
200                    log::error!("{}", err.message);
201                    errors.push(err);
202                }
203            };
204        }
205        (traces, errors)
206    }
207}