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