diff --git a/boot_diags/src/components.rs b/boot_diags/src/components.rs index 6454104..928e99a 100644 --- a/boot_diags/src/components.rs +++ b/boot_diags/src/components.rs @@ -1,2 +1,17 @@ +// This file is part of Deja-Vu. +// +// Deja-Vu is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Deja-Vu is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Deja-Vu. If not, see . +// pub mod logview; pub mod progress; diff --git a/boot_diags/src/scan.rs b/boot_diags/src/scan.rs index 7d9354b..1e80185 100644 --- a/boot_diags/src/scan.rs +++ b/boot_diags/src/scan.rs @@ -1,3 +1,18 @@ +// This file is part of Deja-Vu. +// +// Deja-Vu is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Deja-Vu is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Deja-Vu. If not, see . +// use color_eyre::Result; use core::system::disk::PartitionTableType; use core::tasks::Tasks; diff --git a/deja_vu/src/app.rs b/deja_vu/src/app.rs index 2b2d516..75194ad 100644 --- a/deja_vu/src/app.rs +++ b/deja_vu/src/app.rs @@ -467,9 +467,8 @@ impl App { self.set_mode(new_mode)?; self.action_tx .send(Action::UpdateFooter(build_footer_string(self.cur_mode)))?; - self.action_tx.send(build_left_items(self, self.cur_mode))?; - self.action_tx - .send(build_right_items(self, self.cur_mode))?; + self.action_tx.send(build_left_items(self))?; + self.action_tx.send(build_right_items(self))?; match new_mode { Mode::SelectTableType | Mode::Confirm => { // Select source/dest disks @@ -647,12 +646,12 @@ fn build_footer_string(cur_mode: Mode) -> String { } } -fn build_left_items(app: &App, cur_mode: Mode) -> Action { +fn build_left_items(app: &App) -> Action { let select_type: SelectionType; let title: String; let mut items = Vec::new(); let mut labels: Vec = Vec::new(); - match cur_mode { + match app.cur_mode { Mode::Home => { select_type = SelectionType::Loop; title = String::from("Home"); @@ -724,11 +723,11 @@ fn build_left_items(app: &App, cur_mode: Mode) -> Action { Action::UpdateLeft(title, labels, items, select_type) } -fn build_right_items(app: &App, cur_mode: Mode) -> Action { +fn build_right_items(app: &App) -> Action { let mut items = Vec::new(); let mut labels: Vec> = Vec::new(); let mut start_index = 0; - match cur_mode { + match app.cur_mode { Mode::InstallDrivers => { items.push(vec![DVLine { line_parts: vec![String::from("CPU")], diff --git a/win_installer/src/app.rs b/win_installer/src/app.rs index 587ba2a..3c77bc7 100644 --- a/win_installer/src/app.rs +++ b/win_installer/src/app.rs @@ -15,15 +15,23 @@ // use core::{ action::Action, - components::{Component, footer::Footer, left::Left, popup, right::Right, title::Title}, + components::{ + Component, + footer::Footer, + left::{Left, SelectionType}, + popup, + right::Right, + title::Title, + }, config::Config, + line::{DVLine, get_disk_description_right}, state::Mode, + system::{cpu::get_cpu_name, disk::PartitionTableType, drivers}, tasks::{TaskType, Tasks}, tui::{Event, Tui}, }; use std::{ iter::zip, - path::PathBuf, sync::{Arc, Mutex}, }; @@ -32,13 +40,14 @@ use ratatui::{ crossterm::event::KeyEvent, layout::{Constraint, Direction, Layout}, prelude::Rect, + style::Color, }; use tokio::sync::mpsc; use tracing::{debug, info}; -use crate::state::State; +use crate::{components::wim_scan::WimScan, state::State, wim::parse_wim_file}; -pub struct App<'a> { +pub struct App { // TUI action_rx: mpsc::UnboundedReceiver, action_tx: mpsc::UnboundedSender, @@ -50,16 +59,18 @@ pub struct App<'a> { should_suspend: bool, tick_rate: f64, // App - state: State<'a>, - mode: Mode, + state: State, + cur_mode: Mode, tasks: Tasks, } -impl App<'_> { +impl App { pub fn new(tick_rate: f64, frame_rate: f64) -> Result { let (action_tx, action_rx) = mpsc::unbounded_channel(); let disk_list_arc = Arc::new(Mutex::new(Vec::new())); let tasks = Tasks::new(action_tx.clone(), disk_list_arc.clone()); + let state = State::new(disk_list_arc); + let wim_sources = Arc::clone(&state.wim_sources); Ok(Self { // TUI action_rx, @@ -68,6 +79,7 @@ impl App<'_> { Box::new(Title::new("Windows Install Tool")), Box::new(Left::new()), Box::new(Right::new()), + Box::new(WimScan::new(wim_sources)), Box::new(Footer::new()), Box::new(popup::Popup::new()), ], @@ -78,14 +90,14 @@ impl App<'_> { should_suspend: false, tick_rate, // App - mode: Mode::default(), - state: State::new(disk_list_arc), + cur_mode: Mode::default(), + state, tasks, }) } pub fn next_mode(&mut self) -> Mode { - match self.mode { + match self.cur_mode { Mode::Home | Mode::InstallDrivers => Mode::ScanDisks, Mode::ScanDisks => Mode::SelectDisks, Mode::SelectDisks => Mode::SelectTableType, @@ -112,6 +124,41 @@ impl App<'_> { } } + pub fn set_mode(&mut self, new_mode: Mode) -> Result<()> { + info!("Setting mode to {new_mode:?}"); + self.cur_mode = new_mode; + match new_mode { + Mode::InstallDrivers => self.state.scan_drivers(), + // Mode::Process => { + // self.action_tx + // .send(Action::DisplayPopup(popup::Type::Info, String::from("...")))?; + // } + Mode::ScanDisks => { + self.state.reset_all(); + if self.tasks.idle() { + self.tasks.add(TaskType::ScanDisks); + } + self.action_tx.send(Action::DisplayPopup( + popup::Type::Info, + String::from("Scanning Disks..."), + ))?; + } + Mode::ScanWinImages => { + // TODO: DELETEME + let mut wim_sources = self.state.wim_sources.lock().unwrap(); + wim_sources + .local + .push(parse_wim_file("/home/twoshirt/Projects/deja-vu/23H2.wim")?); + } + Mode::Done => { + self.action_tx + .send(Action::DisplayPopup(popup::Type::Success, popup::fortune()))?; + } + _ => {} + } + Ok(()) + } + pub async fn run(&mut self) -> Result<()> { let mut tui = Tui::new()? // .mouse(true) // uncomment this line to enable mouse support @@ -172,7 +219,7 @@ impl App<'_> { fn handle_key_event(&mut self, key: KeyEvent) -> Result<()> { let action_tx = self.action_tx.clone(); - let Some(keymap) = self.config.keybindings.get(&self.mode) else { + let Some(keymap) = self.config.keybindings.get(&self.cur_mode) else { return Ok(()); }; if let Some(action) = keymap.get(&vec![key]) { @@ -236,40 +283,78 @@ impl App<'_> { .send(Action::DisplayPopup(popup::Type::Error, msg.clone()))?; self.action_tx.send(Action::SetMode(Mode::Failed))?; } - // Action::Process => { - // // Run selected tool - // if let Some(tool) = self.list.get_selected() { - // info!("Run tool: {:?}", &tool); - // self.tasks.add(build_tool_command(self, &tool)); - // } - // } Action::Resize(w, h) => self.handle_resize(tui, w, h)?, Action::Render => self.render(tui)?, Action::FindWimNetwork => { self.state.reset_network(); + let mut wim_sources = self.state.wim_sources.lock().unwrap(); + wim_sources + .network + .push(parse_wim_file("/home/twoshirt/Projects/deja-vu/23H2.wim")?); + wim_sources + .network + .push(parse_wim_file("/home/twoshirt/Projects/deja-vu/24H2.wim")?); // TODO: Actually scan network! } - Action::SetMode(mode) => { - self.mode = mode; - match mode { - Mode::Done => { - self.action_tx.send(Action::DisplayPopup( - popup::Type::Success, - popup::fortune(), - ))?; - } - Mode::ScanDisks => { - self.state.reset_all(); - } - _ => {} + Action::NextScreen => { + let next_mode = self.next_mode(); + self.action_tx.send(Action::DismissPopup)?; + self.action_tx.send(Action::SetMode(next_mode))?; + } + Action::Process => match self.cur_mode { + Mode::Confirm => { + self.action_tx.send(Action::NextScreen)?; } + Mode::Done => { + self.action_tx.send(Action::Quit)?; + } + _ => {} + }, + Action::Select(one, _two) => match self.cur_mode { + Mode::InstallDrivers => { + if let Some(index) = one + && let Some(driver) = self.state.driver_list.get(index).cloned() + { + drivers::load(&driver.inf_paths); + self.state.driver = Some(driver); + } + } + Mode::SelectDisks => { + self.state.disk_index_dest = one; + } + Mode::SelectTableType => { + self.state.table_type = { + if let Some(index) = one { + match index { + 0 => Some(PartitionTableType::Guid), + 1 => Some(PartitionTableType::Legacy), + index => { + panic!("Failed to select PartitionTableType: {index}") + } + } + } else { + None + } + } + } + Mode::SelectWinImage => { + // TODO: FIXME + // PLAN: Abuse Action::Select to send (file_index, image_index) to set all at once + // self.state.wim_file_index = TODO; + // self.state.wim_image_index = TODO; + } + _ => {} + }, + Action::SetMode(mode) => { + self.set_mode(mode)?; self.action_tx.send(Action::UpdateFooter(String::from( "(Enter) to select / (t) for terminal / (p) to power off / (r) to restart", )))?; - //self.action_tx.send(build_left_items(self))?; - //self.action_tx.send(build_right_items(self))?; + self.action_tx.send(build_left_items(self))?; + self.action_tx.send(build_right_items(self))?; self.action_tx.send(Action::Select(None, None))?; } + Action::TasksComplete => self.action_tx.send(Action::NextScreen)?, _ => {} } for component in &mut self.components { @@ -289,8 +374,10 @@ impl App<'_> { fn render(&mut self, tui: &mut Tui) -> Result<()> { tui.draw(|frame| { - if let [header, _body, footer, left, right, popup] = get_chunks(frame.area())[..] { - let component_areas = vec![header, left, right, footer, popup]; + if let [header, _body, footer, center, left, right, popup] = + get_chunks(frame.area())[..] + { + let component_areas = vec![header, center, left, right, footer, popup]; for (component, area) in zip(self.components.iter_mut(), component_areas) { if let Err(err) = component.draw(frame, area) { let _ = self @@ -304,6 +391,140 @@ impl App<'_> { } } +fn build_left_items(app: &App) -> Action { + let select_type: SelectionType; + let title: String; + let mut items = Vec::new(); + let mut labels: Vec = Vec::new(); + match app.cur_mode { + Mode::Home => { + select_type = SelectionType::Loop; + title = String::from("Home"); + } + Mode::InstallDrivers => { + select_type = SelectionType::One; + title = String::from("Install Drivers"); + app.state + .driver_list + .iter() + .for_each(|driver| items.push(driver.to_string())); + } + Mode::Process => { + select_type = SelectionType::Loop; + title = String::from("Processing"); + // TODO: FIXME + } + Mode::ScanWinImages => { + select_type = SelectionType::Loop; + title = String::from("Scanning"); + // TODO: FIXME + } + Mode::SelectWinImage => { + select_type = SelectionType::One; + title = String::from("Select Windows Image"); + // TODO: FIXME + } + Mode::SelectDisks => { + select_type = SelectionType::One; + title = String::from("Select Destination Disk"); + let disk_list = app.state.disk_list.lock().unwrap(); + disk_list + .iter() + .for_each(|disk| items.push(disk.description.to_string())); + } + Mode::SelectTableType => { + select_type = SelectionType::One; + title = String::from("Select Partition Table Type"); + items.push(format!("{}", PartitionTableType::Guid)); + items.push(format!("{}", PartitionTableType::Legacy)); + } + Mode::SetUserName => { + select_type = SelectionType::Loop; + title = String::from("Customize"); + // TODO: FIXME + } + Mode::Confirm => { + select_type = SelectionType::Loop; + title = String::from("Confirm Selections"); + } + Mode::ScanDisks | Mode::PreClone | Mode::Clone | Mode::PostClone => { + select_type = SelectionType::Loop; + title = String::from("Processing"); + } + Mode::Done | Mode::Failed => { + select_type = SelectionType::Loop; + title = String::from("Done"); + } + // Invalid states + Mode::BootDiags + | Mode::BootScan + | Mode::BootSetup + | Mode::DiagMenu + | Mode::InjectDrivers + | Mode::LogView + | Mode::PEMenu + | Mode::SelectParts + | Mode::SetBootMode => panic!("This shouldn't happen?"), + }; + Action::UpdateLeft(title, labels, items, select_type) +} + +fn build_right_items(app: &App) -> Action { + let mut items = Vec::new(); + let mut labels: Vec> = Vec::new(); + let mut start_index = 0; + match app.cur_mode { + Mode::InstallDrivers => { + items.push(vec![DVLine { + line_parts: vec![String::from("CPU")], + line_colors: vec![Color::Cyan], + }]); + items.push(vec![DVLine { + line_parts: vec![get_cpu_name()], + line_colors: vec![Color::Reset], + }]); + start_index = 2; + } + Mode::ScanWinImages + | Mode::SelectWinImage + | Mode::SetUserName + | Mode::SelectDisks + | Mode::SelectTableType + | Mode::Confirm => { + // Labels + let dest_dv_line = DVLine { + line_parts: vec![ + String::from("Dest"), + String::from(" (WARNING: ALL DATA WILL BE DELETED!)"), + ], + line_colors: vec![Color::Cyan, Color::Red], + }; + if let Some(table_type) = &app.state.table_type { + // Show table type + let type_str = match table_type { + PartitionTableType::Guid => "GPT", + PartitionTableType::Legacy => "MBR", + }; + labels.push(vec![ + dest_dv_line, + DVLine { + line_parts: vec![format!(" (Will be formatted {type_str})")], + line_colors: vec![Color::Yellow], + }, + ]); + } else { + labels.push(vec![dest_dv_line]); + } + let disk_list = app.state.disk_list.lock().unwrap(); + disk_list + .iter() + .for_each(|disk| items.push(get_disk_description_right(disk, &None))); + } + _ => {} + } + Action::UpdateRight(labels, start_index, items) +} + fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect { // Cut the given rectangle into three vertical pieces let popup_layout = Layout::default() @@ -341,16 +562,20 @@ fn get_chunks(r: Rect) -> Vec { .split(r) .to_vec(), ); + let center = centered_rect(90, 90, chunks[1]); // Left/Right chunks.extend( Layout::default() .direction(Direction::Horizontal) .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) - .split(centered_rect(90, 90, chunks[1])) + .split(center) .to_vec(), ); + // Center + chunks.push(center); + // Popup chunks.push(centered_rect(60, 25, r)); diff --git a/win_installer/src/components.rs b/win_installer/src/components.rs new file mode 100644 index 0000000..51833fd --- /dev/null +++ b/win_installer/src/components.rs @@ -0,0 +1,16 @@ +// This file is part of Deja-Vu. +// +// Deja-Vu is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Deja-Vu is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Deja-Vu. If not, see . +// +pub mod wim_scan; diff --git a/win_installer/src/components/wim_scan.rs b/win_installer/src/components/wim_scan.rs new file mode 100644 index 0000000..039177e --- /dev/null +++ b/win_installer/src/components/wim_scan.rs @@ -0,0 +1,186 @@ +// This file is part of Deja-Vu. +// +// Deja-Vu is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Deja-Vu is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Deja-Vu. If not, see . +// +use core::{action::Action, components::Component, config::Config, state::Mode}; +use std::{ + iter::zip, + sync::{Arc, Mutex}, +}; + +use color_eyre::Result; +use crossterm::event::KeyEvent; +use ratatui::{ + prelude::*, + widgets::{Block, Borders, Clear, HighlightSpacing, List, ListItem, Padding, Paragraph}, +}; +use tokio::sync::mpsc::UnboundedSender; + +use crate::wim::WimSources; + +#[derive(Default)] +pub struct WimScan { + command_tx: Option>, + config: Config, + mode: Mode, + scan_network: bool, + wim_sources: Arc>, +} + +impl WimScan { + #[must_use] + pub fn new(wim_sources: Arc>) -> Self { + let wim_sources = wim_sources.clone(); + Self { + wim_sources, + ..Default::default() + } + } +} + +impl Component for WimScan { + fn handle_key_event(&mut self, key: KeyEvent) -> Result> { + let _ = key; // to appease clippy + Ok(None) + } + + fn register_action_handler(&mut self, tx: UnboundedSender) -> Result<()> { + self.command_tx = Some(tx); + Ok(()) + } + + fn register_config_handler(&mut self, config: Config) -> Result<()> { + self.config = config; + Ok(()) + } + + fn update(&mut self, action: Action) -> Result> { + match action { + Action::FindWimNetwork => self.scan_network = true, + Action::SetMode(new_mode) => { + self.mode = new_mode; + } + _ => {} + } + Ok(None) + } + + fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { + if self.mode != Mode::ScanWinImages { + return Ok(()); + } + frame.render_widget(Clear, area); + + // Prep + let [left, right] = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) + .areas(area); + let [left_title, left_body] = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Length(1), Constraint::Min(1)]) + .areas(left); + let [right_title, right_body] = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Length(1), Constraint::Min(1)]) + .areas(right); + let [local_area, network_area] = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) + .areas(left_body); + + // Titles + let titles = vec![ + Paragraph::new(Line::from("Scanning").centered()) + .block(Block::default().borders(Borders::NONE)), + Paragraph::new(Line::from("Info").centered()) + .block(Block::default().borders(Borders::NONE)), + ]; + for (title, area) in zip(titles, [left_title, right_title]) { + frame.render_widget(title, area); + } + + // Local Scan + let local_area = if self.scan_network { + local_area + } else { + left_body + }; + let local_words = "Lorem ipsum dolor sit amet".split_whitespace().to_owned(); + let local_list = List::new(local_words) + .block( + Block::default() + .borders(Borders::ALL) + .padding(Padding::new(1, 1, 1, 1)), + ) + .highlight_spacing(HighlightSpacing::Always) + .highlight_style(Style::new().green().bold()) + .highlight_symbol(" --> ") + .repeat_highlight_symbol(false); + frame.render_widget(local_list, local_area); + + // Network Scan + if self.scan_network { + let network_words = "Adipiscing elit quisque faucibus ex sapien" + .split_whitespace() + .to_owned(); + let network_list = List::new(network_words) + .block( + Block::default() + .borders(Borders::ALL) + .padding(Padding::new(1, 1, 1, 1)), + ) + .highlight_spacing(HighlightSpacing::Always) + .highlight_style(Style::new().green().bold()) + .highlight_symbol(" --> ") + .repeat_highlight_symbol(false); + frame.render_widget(network_list, network_area); + } + + // WIM Info + let wim_sources = self.wim_sources.lock().unwrap(); + let mut right_list = Vec::new(); + if !wim_sources.network.is_empty() { + right_list.push(ListItem::new("-- Network --\n\n")); + right_list.extend( + wim_sources + .network + .iter() + .map(|wimfile| ListItem::new(format!("{}\n\n", wimfile.path))), + ); + right_list.push(ListItem::new("")); + } + right_list.push(ListItem::new("-- Local --\n\n")); + right_list.extend( + wim_sources + .local + .iter() + .map(|wimfile| ListItem::new(format!("{}\n\n", wimfile.path))), + ); + let right_list = List::new(right_list) + .block( + Block::default() + .borders(Borders::ALL) + .padding(Padding::new(1, 1, 1, 1)), + ) + .highlight_spacing(HighlightSpacing::Always) + .highlight_style(Style::new().green().bold()) + .highlight_symbol(" --> ") + .repeat_highlight_symbol(false); + frame.render_widget(right_list, right_body); + + // Done + Ok(()) + } +} diff --git a/win_installer/src/main.rs b/win_installer/src/main.rs index 88a0f38..ab05a63 100644 --- a/win_installer/src/main.rs +++ b/win_installer/src/main.rs @@ -19,6 +19,7 @@ use color_eyre::Result; use crate::app::App; mod app; +mod components; mod state; mod wim; diff --git a/win_installer/src/state.rs b/win_installer/src/state.rs index 6156c61..885485b 100644 --- a/win_installer/src/state.rs +++ b/win_installer/src/state.rs @@ -16,23 +16,26 @@ use std::sync::{Arc, Mutex}; -use core::system::{disk::Disk, drivers}; +use core::system::{ + disk::{Disk, PartitionTableType}, + drivers, +}; use crate::wim::WimSources; #[derive(Debug, Default)] -pub struct State<'a> { +pub struct State { pub disk_index_dest: Option, pub disk_list: Arc>>, pub driver: Option, pub driver_list: Vec, - pub part_index_boot: Option, + pub table_type: Option, pub wim_file_index: Option, pub wim_image_index: Option, - pub wim_sources: Arc>>, + pub wim_sources: Arc>, } -impl State<'_> { +impl State { pub fn new(disk_list: Arc>>) -> Self { State { disk_list, @@ -42,7 +45,6 @@ impl State<'_> { pub fn reset_all(&mut self) { self.disk_index_dest = None; - self.part_index_boot = None; self.wim_file_index = None; self.wim_image_index = None; if let Ok(mut sources) = self.wim_sources.lock() { diff --git a/win_installer/src/wim.rs b/win_installer/src/wim.rs index 6a8326f..3e407be 100644 --- a/win_installer/src/wim.rs +++ b/win_installer/src/wim.rs @@ -48,18 +48,18 @@ static WIN_BUILDS: LazyLock> = LazyLock::new(|| { }); #[derive(Debug)] -pub struct WimFile<'a> { - path: &'a Path, - images: Vec, +pub struct WimFile { + pub path: String, + pub images: Vec, } #[derive(Clone, Debug, Default)] pub struct WimImage { - build: String, - index: String, - name: String, - spbuild: String, - version: String, + pub build: String, + pub index: String, + pub name: String, + pub spbuild: String, + pub version: String, } impl WimImage { @@ -89,12 +89,12 @@ impl fmt::Display for WimImage { } #[derive(Debug, Default)] -pub struct WimSources<'a> { - pub local: Vec>, - pub network: Vec>, +pub struct WimSources { + pub local: Vec, + pub network: Vec, } -impl WimSources<'_> { +impl WimSources { pub fn new() -> Self { Default::default() } @@ -124,10 +124,9 @@ fn get_wim_xml(wim_file: &str) -> std::io::Result { Ok(file) } -fn parse_wim_file(wim_file: &str) -> std::io::Result> { +pub fn parse_wim_file(wim_file: &str) -> std::io::Result { let mut wim_images: Vec = Vec::new(); - let wim_path = Path::new(wim_file); - if !wim_path.exists() { + if !Path::new(wim_file).exists() { return Err(std::io::Error::new( std::io::ErrorKind::NotFound, "Failed to read WIM file", @@ -146,7 +145,6 @@ fn parse_wim_file(wim_file: &str) -> std::io::Result> { Ok(XmlEvent::StartElement { name, attributes, .. }) => { - println!("{:spaces$}+{name}", "", spaces = depth * 2); depth += 1; current_element = name.local_name.to_uppercase(); @@ -164,20 +162,16 @@ fn parse_wim_file(wim_file: &str) -> std::io::Result> { let build = char_data.trim(); image.build = build.to_string(); image.version = WIN_BUILDS.get(build).map_or("", |v| v).to_string(); - println!("{:spaces$} {char_data}", "", spaces = (depth + 1) * 2); } if current_element == "NAME" { image.name = char_data.trim().to_string(); - println!("{:spaces$} {char_data}", "", spaces = (depth + 1) * 2); } if current_element == "SPBUILD" { image.spbuild = char_data.trim().to_string(); - println!("{:spaces$} {char_data}", "", spaces = (depth + 1) * 2); } } Ok(XmlEvent::EndElement { name }) => { depth -= 1; - println!("{:spaces$}-{name}", "", spaces = depth * 2); if name.local_name.to_uppercase() == "IMAGE" { // Append image to list @@ -191,14 +185,13 @@ fn parse_wim_file(wim_file: &str) -> std::io::Result> { } } Err(e) => { - eprintln!("Error: {e}"); break; } _ => {} } } let wim_file = WimFile { - path: wim_path, + path: wim_file.to_string(), images: wim_images, };