From f272b0c482b8942e8dee2b870ff92287e89f4a6b Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Sat, 15 Feb 2025 17:06:19 -0800 Subject: [PATCH] WIP: New data structs for boot diags --- boot_diags/src/app.rs | 129 ++++++++++++++++++++++++++++++++++------ boot_diags/src/diags.rs | 56 +++++++++++++++++ boot_diags/src/main.rs | 1 + config/config.json5 | 7 +++ core/src/action.rs | 2 +- core/src/state.rs | 1 + deja_vu/src/app.rs | 75 ++++++++++++----------- 7 files changed, 217 insertions(+), 54 deletions(-) create mode 100644 boot_diags/src/diags.rs diff --git a/boot_diags/src/app.rs b/boot_diags/src/app.rs index b0d7a05..509bcfd 100644 --- a/boot_diags/src/app.rs +++ b/boot_diags/src/app.rs @@ -13,6 +13,7 @@ // You should have received a copy of the GNU General Public License // along with Deja-Vu. If not, see . // +use crate::diags::Groups as DiagGroups; use core::{ action::Action, components::{ @@ -27,7 +28,7 @@ use core::{ cpu::get_cpu_name, drivers, }, - tasks::{TaskType, Tasks}, + tasks::{Task, TaskResult, TaskType, Tasks}, tui::{Event, Tui}, }; use std::{ @@ -44,7 +45,7 @@ use ratatui::{ style::Color, }; use tokio::sync::mpsc; -use tracing::{debug, info}; +use tracing::{debug, info, warn}; pub struct App { // TUI @@ -52,6 +53,7 @@ pub struct App { action_tx: mpsc::UnboundedSender, components: Vec>, config: Config, + diag_groups: DiagGroups, frame_rate: f64, last_tick_key_events: Vec, should_quit: bool, @@ -74,7 +76,7 @@ impl App { let tasks = Tasks::new(action_tx.clone(), disk_list_arc.clone()); let mut list = StatefulList::default(); list.set_items(vec![ - Mode::BootDiags, + Mode::BootScan, Mode::BootSetup, Mode::InjectDrivers, Mode::SetBootMode, @@ -92,6 +94,7 @@ impl App { Box::new(popup::Popup::new()), ], config: Config::new()?, + diag_groups: DiagGroups::new(), frame_rate, last_tick_key_events: Vec::new(), should_quit: false, @@ -134,7 +137,8 @@ impl App { Mode::ScanDisks => Mode::SelectDisks, Mode::SelectDisks => Mode::SelectParts, Mode::SelectParts => Mode::DiagMenu, - Mode::BootDiags | Mode::BootSetup => Mode::DiagMenu, // TODO: add Mode::ProgressReport + Mode::BootDiags | Mode::BootSetup => Mode::DiagMenu, + Mode::BootScan => Mode::BootDiags, Mode::InjectDrivers | Mode::SetBootMode => Mode::DiagMenu, Mode::Done => Mode::DiagMenu, Mode::Failed => Mode::Failed, @@ -175,6 +179,15 @@ impl App { self.selections[1] = None; self.list.select_first_item(); } + Mode::BootScan => { + if self.tasks.idle() { + self.tasks.add(TaskType::Sleep); + } + self.action_tx.send(Action::DisplayPopup( + popup::Type::Info, + String::from("Gathering info..."), + ))?; + } Mode::InjectDrivers | Mode::InstallDrivers => self.clone.scan_drivers(), Mode::ScanDisks => { if self.tasks.idle() { @@ -294,16 +307,8 @@ impl App { Action::Tick => { self.last_tick_key_events.drain(..); // Check background task(s) - match self.cur_mode { - Mode::BootDiags => { - if let Some(task) = self.tasks.poll()? { - // TODO: Impl logic - } - } - Mode::ScanDisks => { - let _ = self.tasks.poll()?; - } - _ => {} + if let Some(task) = self.tasks.poll()? { + self.handle_task(&task)?; } } Action::Quit => self.should_quit = true, @@ -317,6 +322,7 @@ impl App { .send(Action::DisplayPopup(popup::Type::Error, msg.clone()))?; self.action_tx.send(Action::SetMode(Mode::Failed))?; } + Action::BootScan => self.action_tx.send(Action::SetMode(Mode::BootScan))?, Action::InstallDriver => { self.action_tx.send(Action::SetMode(Mode::InstallDrivers))?; } @@ -407,6 +413,94 @@ impl App { Ok(()) } + fn handle_task(&mut self, task: &Task) -> Result<()> { + match self.cur_mode { + Mode::BootDiags => { + if let TaskType::Command(cmd_path, cmd_args) = &task.task_type { + let mut cmd_name = ""; + if let Some(path) = cmd_path.file_name() { + if let Some(cmd_str) = path.to_str() { + cmd_name = cmd_str; + } + }; + match cmd_name { + "bcdedit" => { + // Boot config + let title = "Boot Files"; + if let Some(result) = &task.result { + let passed: bool; + let info: String; + match result { + TaskResult::Error(msg) => { + passed = false; + info = msg.to_owned(); + } + TaskResult::Output(stdout, stderr, success) => { + passed = *success; + let div = if !(stdout.is_empty() || stderr.is_empty()) { + "\n\n-----------\n\n" + } else { + "" + }; + info = format!("{stdout}{div}{stderr}"); + } + } + self.diag_groups + .update(title.to_string(), passed, info.to_owned()); + } + } + "manage-bde" => { + // Bitlocker + } + "chkdsk" => { + // File system + } + "reg" => { + // Registry + } + "dir" => { + // Files/Folders + } + _ => { + warn!("Unrecognized command: {:?}", &cmd_path) + } + } + } + } + _ => { + match task.task_type { + TaskType::Command(_, _) | TaskType::Diskpart(_) => { + // Check result + if let Some(result) = &task.result { + match result { + TaskResult::Error(msg) => { + self.action_tx + .send(Action::Error(format!("{task:?} Failed: {msg}")))?; + } + TaskResult::Output(stdout, stderr, success) => { + if !success { + let msg = if !stdout.is_empty() { + stdout.clone() + } else if !stderr.is_empty() { + stderr.clone() + } else { + String::from("Unknown Error") + }; + self.action_tx.send(Action::Error(format!( + "{task:?} Failed: {msg}" + )))?; + } + } + } + } + } + _ => {} + } + } + } + Ok(()) + } + fn render(&mut self, tui: &mut Tui) -> Result<()> { tui.draw(|frame| { if let [header, _body, footer, left, right, popup] = get_chunks(frame.area())[..] { @@ -484,7 +578,8 @@ fn get_chunks(r: Rect) -> Vec { fn build_footer_string(cur_mode: Mode) -> String { match cur_mode { - Mode::BootDiags | Mode::BootSetup | Mode::Home | Mode::PEMenu | Mode::ScanDisks => { + Mode::BootDiags => String::from("(r) to refresh / (q) to quit"), + Mode::BootScan | Mode::BootSetup | Mode::Home | Mode::PEMenu | Mode::ScanDisks => { String::from("(q) to quit") } Mode::InstallDrivers | Mode::InjectDrivers | Mode::SetBootMode => { @@ -547,7 +642,7 @@ fn build_left_items(app: &App) -> Action { .iter() .for_each(|disk| items.push(disk.description.to_string())); } - Mode::ScanDisks => { + Mode::BootScan | Mode::ScanDisks => { select_num = 0; title = String::from("Processing"); } @@ -747,7 +842,7 @@ fn build_right_items(app: &App) -> Action { fn get_mode_strings(mode: Mode) -> (String, String) { match mode { - Mode::BootDiags => ( + Mode::BootScan | Mode::BootDiags => ( String::from("Boot Diagnostics"), String::from("Check for common Windows boot issues"), ), diff --git a/boot_diags/src/diags.rs b/boot_diags/src/diags.rs new file mode 100644 index 0000000..33c5275 --- /dev/null +++ b/boot_diags/src/diags.rs @@ -0,0 +1,56 @@ +// 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 std::collections::HashMap; + +pub struct Groups { + items: HashMap, +} + +impl Groups { + pub fn new() -> Self { + Groups { + items: HashMap::new(), + } + } + + pub fn update(&mut self, title: String, passed: bool, info: String) { + if let Some(line) = self.items.get_mut(&title) { + line.update(passed, info); + } else { + self.items.insert( + title.clone(), + Line { + title, + passed, + info: vec![info], + }, + ); + } + } +} + +pub struct Line { + title: String, + passed: bool, + info: Vec, +} + +impl Line { + pub fn update(&mut self, passed: bool, info: String) { + self.passed &= passed; // We fail if any tests in this group fail + self.info.push(info); + } +} diff --git a/boot_diags/src/main.rs b/boot_diags/src/main.rs index 82b0996..3cefdda 100644 --- a/boot_diags/src/main.rs +++ b/boot_diags/src/main.rs @@ -20,6 +20,7 @@ use core; use crate::app::App; mod app; +mod diags; #[tokio::main] async fn main() -> Result<()> { diff --git a/config/config.json5 b/config/config.json5 index 15471b4..802c914 100644 --- a/config/config.json5 +++ b/config/config.json5 @@ -109,6 +109,13 @@ "": "Process", "": "KeyUp", "": "KeyDown", + "": "BootScan", + "": "Quit", + "": "Quit", + "": "Quit", + "": "Suspend" + }, + "BootScan": { "": "Quit", "": "Quit", "": "Quit", diff --git a/core/src/action.rs b/core/src/action.rs index 5db5638..486cdbb 100644 --- a/core/src/action.rs +++ b/core/src/action.rs @@ -21,7 +21,7 @@ use crate::{components::popup::Type, line::DVLine, state::Mode, system::disk::Di #[derive(Debug, Clone, PartialEq, Eq, Display, Serialize, Deserialize)] pub enum Action { // App (Boot-Diags) - // TODO: Add actions? + BootScan, // App (Clone) Highlight(usize), InstallDriver, diff --git a/core/src/state.rs b/core/src/state.rs index e4bc8c9..8c8eb8d 100644 --- a/core/src/state.rs +++ b/core/src/state.rs @@ -33,6 +33,7 @@ pub enum Mode { // Boot Diags DiagMenu, BootDiags, + BootScan, BootSetup, InjectDrivers, SetBootMode, diff --git a/deja_vu/src/app.rs b/deja_vu/src/app.rs index 0c3cb20..293be47 100644 --- a/deja_vu/src/app.rs +++ b/deja_vu/src/app.rs @@ -26,7 +26,7 @@ use core::{ boot, cpu::get_cpu_name, disk::PartitionTableType, diskpart::build_dest_format_script, drivers, }, - tasks::{TaskResult, TaskType, Tasks}, + tasks::{Task, TaskResult, TaskType, Tasks}, tui::{Event, Tui}, }; use std::{ @@ -116,6 +116,7 @@ impl App { | Mode::PostClone => None, // Invalid states Mode::BootDiags + | Mode::BootScan | Mode::BootSetup | Mode::DiagMenu | Mode::InjectDrivers @@ -140,6 +141,7 @@ impl App { Mode::Failed => Mode::Failed, // Invalid states Mode::BootDiags + | Mode::BootScan | Mode::BootSetup | Mode::DiagMenu | Mode::InjectDrivers @@ -373,41 +375,9 @@ impl App { match action { Action::Tick => { self.last_tick_key_events.drain(..); - match self.cur_mode { - Mode::ScanDisks | Mode::PreClone | Mode::Clone | Mode::PostClone => { - // Check background task (Action::NextScreen is sent when task(s) are done) - if let Some(task) = self.tasks.poll()? { - match task.task_type { - TaskType::Command(_, _) | TaskType::Diskpart(_) => { - if let Some(result) = &task.result { - match result { - TaskResult::Error(msg) => { - self.action_tx.send(Action::Error(format!( - "{task:?} Failed: {msg}" - )))?; - } - TaskResult::Output(stdout, stderr, success) => { - if !success { - let msg = if !stdout.is_empty() { - stdout.clone() - } else if !stderr.is_empty() { - stderr.clone() - } else { - String::from("Unknown Error") - }; - self.action_tx.send(Action::Error( - format!("{task:?} Failed: {msg}"), - ))?; - } - } - } - } - } - _ => {} - } - } - } - _ => {} + // Check background task(s) + if let Some(task) = self.tasks.poll()? { + self.handle_task(&task)?; } } Action::Quit => self.should_quit = true, @@ -548,6 +518,37 @@ impl App { Ok(()) } + fn handle_task(&mut self, task: &Task) -> Result<()> { + match task.task_type { + TaskType::Command(_, _) | TaskType::Diskpart(_) => { + // Check result + if let Some(result) = &task.result { + match result { + TaskResult::Error(msg) => { + self.action_tx + .send(Action::Error(format!("{task:?} Failed: {msg}")))?; + } + TaskResult::Output(stdout, stderr, success) => { + if !success { + let msg = if !stdout.is_empty() { + stdout.clone() + } else if !stderr.is_empty() { + stderr.clone() + } else { + String::from("Unknown Error") + }; + self.action_tx + .send(Action::Error(format!("{task:?} Failed: {msg}")))?; + } + } + } + } + } + _ => {} + } + Ok(()) + } + fn render(&mut self, tui: &mut Tui) -> Result<()> { tui.draw(|frame| { if let [header, _body, footer, left, right, popup] = get_chunks(frame.area())[..] { @@ -638,6 +639,7 @@ fn build_footer_string(cur_mode: Mode) -> String { Mode::Done | Mode::Failed => String::from("(Enter) or (q) to quit"), // Invalid states Mode::BootDiags + | Mode::BootScan | Mode::BootSetup | Mode::DiagMenu | Mode::InjectDrivers @@ -708,6 +710,7 @@ fn build_left_items(app: &App, cur_mode: Mode) -> Action { } // Invalid states Mode::BootDiags + | Mode::BootScan | Mode::BootSetup | Mode::DiagMenu | Mode::InjectDrivers