mame_parser/core/file_handling/file_reader.rs
1use crate::helpers::file_system_helpers::{find_file_with_pattern, WORKSPACE_PATHS};
2use crate::{
3 core::models::{
4 callback_progress::{CallbackType, ProgressCallback, ProgressInfo, SharedProgressCallback},
5 core_models::Machine,
6 mame_data_types::{get_data_type_details, MameDataType},
7 },
8 helpers::callback_progress_helper::get_progress_info,
9};
10use std::collections::HashMap;
11use std::error::Error;
12use std::path::Path;
13use std::sync::Arc;
14use std::thread;
15
16/// Reads and processes a specific MAME data file based on the provided data type.
17///
18/// This function handles the retrieval of a MAME data file from the extracted folder and processes it
19/// by reading its contents and returning a map of machine details. It first checks if the required
20/// data file is present in the expected location and then reads the file using a specialized function
21/// for the provided `MameDataType`. Progress updates and messages are provided via a callback function.
22///
23/// # Parameters
24/// - `data_type`: The `MameDataType` that specifies which type of MAME data file to read (e.g., ROMs, DAT files).
25/// - `workspace_path`: A reference to a `Path` representing the base directory where the data file is located.
26/// - `progress_callback`: A callback function of type `ProgressCallback` that tracks progress and provides status updates.
27/// The callback receives a `ProgressInfo` struct containing `progress`, `total`, `message`, and `callback_type`.
28///
29/// # Returns
30/// Returns a `Result<HashMap<String, Machine>, Box<dyn Error + Send + Sync>>`:
31/// - On success: Contains a `HashMap` where the keys are machine names and the values are `Machine` structs representing detailed information about each MAME machine.
32/// - On failure: Contains an error if the data file is not found, cannot be read, or if there are issues accessing the file system.
33///
34/// # Errors
35/// This function will return an error if:
36/// - The data file is not found in the expected location.
37/// - The data file cannot be read due to permission issues or file corruption.
38/// - There are errors in processing the data file content using the corresponding read function.
39///
40/// # Callback
41/// The progress callback function provides real-time updates on the reading process and other status information. It receives:
42/// - `progress`: The current progress of the operation (e.g., number of entries).
43/// - `total`: The total number of items to be processed.
44/// - `message`: A status message indicating the current operation (e.g., "Checking if data file is present", "Reading file").
45/// — `callback_type`: The type of callback, such as `CallbackType::Info`, `CallbackType::Error`, `CallbackType::Progress`, or `CallbackType::Finish`.
46///
47/// # Example
48#[doc = docify::embed!("examples/read_file.rs", main)]
49///
50pub fn read_file(
51 data_type: MameDataType,
52 workspace_path: &Path,
53 progress_callback: ProgressCallback,
54) -> Result<HashMap<String, Machine>, Box<dyn Error + Send + Sync>> {
55 // Retrieves the details for a given `MameDataType`
56 let data_type_details = get_data_type_details(data_type);
57 // Set path where data file is located
58 let extract_folder = workspace_path
59 .join(WORKSPACE_PATHS.extract_path)
60 .join(data_type_details.name.to_lowercase());
61
62 // Checks if data file is present in the extract folder
63 progress_callback(get_progress_info(
64 format!(
65 "Checking if data file for {} is present",
66 data_type_details.name
67 )
68 .as_str(),
69 ));
70
71 let existing_data_file = find_file_with_pattern(
72 &extract_folder.to_str().unwrap(),
73 &data_type_details.data_file_pattern,
74 );
75
76 if let Err(err) = existing_data_file {
77 progress_callback(ProgressInfo {
78 progress: 0,
79 total: 0,
80 message: format!("Data file for {} not found", data_type_details.name),
81 callback_type: CallbackType::Error,
82 });
83
84 return Err(err.into());
85 }
86
87 let file_path = existing_data_file.unwrap();
88
89 let machines = (data_type_details.read_function)(&file_path, progress_callback)?;
90
91 Ok(machines)
92}
93
94/// Reads and processes all MAME data files available for the specified workspace path.
95///
96/// This function manages the concurrent reading of multiple MAME data files. For each `MameDataType`,
97/// it spawns a separate thread to handle the file reading process. The function waits for all threads
98/// to complete and then combines the results into a single `HashMap` of machine details. Progress updates
99/// and messages are provided via a shared callback function.
100///
101/// # Parameters
102/// - `workspace_path`: A reference to a `Path` representing the base directory where all data files are located.
103/// - `progress_callback`: A shared callback function of type `SharedProgressCallback` that tracks progress and provides status updates.
104/// The callback receives a `ProgressInfo` struct containing `progress`, `total`, `message`, and `callback_type`.
105///
106/// # Returns
107/// Returns a `Result<HashMap<String, Machine>, Box<dyn Error + Send + Sync>>`:
108/// - On success: Contains a `HashMap` where the keys are machine names and the values are `Machine` structs representing detailed information about each MAME machine.
109/// - On failure: Contains an error if any data file cannot be read, or if there are issues joining the threads.
110///
111/// # Errors
112/// This function will return an error if:
113/// - Any thread fails to complete successfully or panics.
114/// - There are issues reading any data file due to permission problems, file corruption, or missing files.
115/// - There are errors during the merging of machine data, such as data inconsistencies.
116///
117/// # Concurrency
118/// This function uses multiple threads to read MAME data files concurrently. Each thread handles the reading of a specific
119/// data type file (`MameDataType`). The function waits for all threads to complete using `join()`, and any errors encountered
120/// are captured and logged. The shared progress callback is used to provide real-time updates across all threads.
121///
122/// # Callback
123/// The shared progress callback function provides real-time updates on the reading process for each data type and other status information. It receives:
124/// - `progress`: The current progress of the operation for a specific data type (e.g., number of files processed).
125/// - `total`: The total number of items to be processed (if available).
126/// - `message`: A status message indicating the current operation (e.g., "Reading file", "Processing data").
127/// - `callback_type`: The type of callback, such as `CallbackType::Info`, `CallbackType::Error`, `CallbackType::Progress`, or `CallbackType::Finish`.
128///
129/// # Example
130#[doc = docify::embed!("examples/read_files.rs", main)]
131///
132pub fn read_files(
133 workspace_path: &Path,
134 progress_callback: SharedProgressCallback,
135) -> Result<HashMap<String, Machine>, Box<dyn Error + Send + Sync>> {
136 let progress_callback = Arc::clone(&progress_callback);
137
138 let handles: Vec<_> = MameDataType::all_variants()
139 .iter()
140 .map(|&data_type| {
141 let workspace_path = workspace_path.to_path_buf();
142 let progress_callback = Arc::clone(&progress_callback);
143
144 thread::spawn(move || {
145 read_file(
146 data_type,
147 &workspace_path,
148 Box::new(move |progress_info| {
149 progress_callback(data_type, progress_info);
150 }),
151 )
152 })
153 })
154 .collect();
155
156 let mut combined_machines = HashMap::new();
157
158 for handle in handles {
159 match handle.join() {
160 Ok(Ok(machines)) => {
161 for (key, new_machine) in machines {
162 combined_machines
163 .entry(key)
164 .and_modify(|existing_machine: &mut Machine| {
165 existing_machine.combine(&new_machine)
166 })
167 .or_insert(new_machine);
168 }
169 }
170 Ok(Err(err)) => {
171 eprintln!("Error reading file: {:?}", err);
172 }
173 Err(err) => {
174 eprintln!("Error joining thread: {:?}", err);
175 }
176 }
177 }
178
179 Ok(combined_machines)
180}