Major refactor around task groups

This commit is contained in:
2Shirt 2025-05-25 01:11:24 -07:00
parent 081fd22de1
commit 2296b8f274
Signed by: 2Shirt
GPG key ID: 152FAC923B0E132C
8 changed files with 138 additions and 254 deletions

View file

@ -39,7 +39,6 @@ use core::{
tui::{Event, Tui},
};
use std::{
collections::VecDeque,
env,
iter::zip,
path::PathBuf,
@ -54,7 +53,7 @@ use ratatui::{
style::Color,
};
use tokio::sync::mpsc;
use tracing::{debug, info, warn};
use tracing::{debug, info};
pub struct App {
// TUI
@ -76,7 +75,6 @@ pub struct App {
selections: Vec<Option<usize>>,
system32: String,
tasks: Tasks,
task_groups: VecDeque<Option<String>>,
}
impl App {
@ -120,7 +118,6 @@ impl App {
system32: String::new(),
selections: vec![None, None],
tasks,
task_groups: VecDeque::new(),
})
}
@ -401,20 +398,12 @@ impl App {
self.action_tx.send(Action::Select(None, None))?;
}
Action::TaskGroupStart(ref title) => {
// TODO: Verify this isn't broken and/or unused
if self.cur_mode == Mode::BootScan {
info!("TaskGroup: {:?}", self.task_groups.front());
if self.task_groups.front().is_some() {
if self.task_groups.front().unwrap().is_some() {
// None here means that we're in the middle of a group of tasks
// i.e. don't start a new diag line
self.action_tx.send(Action::DiagStartLine {
text: title.clone(),
})?;
}
}
if !self.diag_groups.contains(&title) {
self.diag_groups.update(title.to_owned(), None, None);
}
self.action_tx.send(Action::DiagLineStart {
text: title.clone(),
})?;
self.diag_groups.start(title.to_owned());
}
}
Action::TasksComplete => {
@ -443,70 +432,22 @@ impl App {
fn handle_task(&mut self, task: &Task) -> Result<()> {
info!("Handling Task: {task:?}");
let title: Option<String>;
match self.cur_mode {
Mode::BootScan => {
let task_group = self.task_groups.pop_front();
match &task.task_type {
TaskType::CommandWait(cmd_path, _cmd_args) => {
let mut cmd_name = "";
if let Some(path) = cmd_path.file_name() {
if let Some(cmd_str) = path.to_str() {
cmd_name = cmd_str;
}
};
let diag_type = diags::get_type(cmd_name);
match diag_type {
diags::Type::Bitlocker
| diags::Type::BootConfigData
| diags::Type::Registry
| diags::Type::System => {
title = Some(format!("{diag_type}"));
}
diags::Type::FileSystem => {
title = None;
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;
info = parse_chkdsk(stdout);
}
}
self.diag_groups.update(
String::from("Filesystem"),
Some(passed),
Some(info.to_owned()),
);
}
}
diags::Type::Unknown => {
title = None;
warn!("Unrecognized command: {:?}", &cmd_path);
}
};
}
TaskType::TestPaths(_) => {
title = Some(format!("{}", diags::Type::FileSystem));
}
_ => title = None,
}
if let Some(title_str) = title {
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;
if let Some(result) = &task.result {
let title = self.diag_groups.current_group();
let passed: bool;
let info: String;
match result {
TaskResult::Error(msg) => {
passed = false;
info = msg.to_owned();
}
TaskResult::Output(stdout, stderr, success) => {
passed = *success;
if title == "Filesystem" {
info = parse_chkdsk(stdout);
} else {
let div = if !(stdout.is_empty() || stderr.is_empty()) {
"\n\n-----------\n\n"
} else {
@ -515,37 +456,19 @@ impl App {
info = format!("{stdout}{div}{stderr}");
}
}
self.diag_groups
.update(title_str, Some(passed), Some(info.to_owned()));
if let Some(group) = task_group {
if let Some(wat) = group {
info!("WAT? // {wat:?}");
if passed {
self.action_tx.send(Action::DiagEndLine {
result: DiagResult::Pass,
text: String::from("Pass?"),
})?;
} else {
self.action_tx.send(Action::DiagEndLine {
result: DiagResult::Fail,
text: String::from("Fail?"),
})?;
};
}
}
} else {
// If title was set but there wasn't a result
self.action_tx.send(Action::DiagEndLine {
result: DiagResult::Warn,
text: String::from("Yellow no result?"),
})?;
}
} else {
// title was not set
self.action_tx.send(Action::DiagEndLine {
result: DiagResult::Warn,
text: String::from("Yellow no title?"),
})?;
self.diag_groups.update(title, passed, info);
if passed {
self.action_tx.send(Action::DiagLineUpdate {
result: DiagResult::Pass,
text: String::from("Pass?"),
})?;
} else {
self.action_tx.send(Action::DiagLineUpdate {
result: DiagResult::Fail,
text: String::from("Fail?"),
})?;
};
}
}
_ => {
@ -577,6 +500,11 @@ impl App {
}
}
}
TaskType::GroupEnd { ref label } => {
self.action_tx.send(Action::DiagLineEnd {
text: label.clone(),
})?;
}
_ => {}
}
}
@ -626,7 +554,6 @@ impl App {
],
)],
);
self.task_groups.push_back(Some(String::from("Boot Files")));
}
// Bitlocker
@ -647,7 +574,6 @@ impl App {
),
],
);
self.task_groups.push_back(Some(String::from("Bitlocker")));
// Filesystem Health
let paths: Vec<PathBuf> = [
@ -671,7 +597,6 @@ impl App {
TaskType::TestPaths(paths),
],
);
self.task_groups.push_back(Some(String::from("Filesystem")));
// DISM Health
self.tasks.add_group(
@ -681,8 +606,6 @@ impl App {
vec![format!("{letter_os}:")],
)],
);
self.task_groups
.push_back(Some(String::from("System Files")));
// Registry
self.tasks.add_group(
@ -726,7 +649,6 @@ impl App {
),
],
);
self.task_groups.push_back(Some(String::from("Registry")));
self.tasks.add(TaskType::Sleep); // NOTE: DELETEME
}
}

View file

@ -34,6 +34,7 @@ struct ProgressLine {
name: String,
text: String,
result: DiagResult,
running: bool,
}
impl ProgressLine {
@ -70,17 +71,18 @@ impl Progress {
impl Component for Progress {
fn update(&mut self, action: Action) -> Result<Option<Action>> {
match action {
Action::DiagStartLine { text } => {
info!("Caught Action::DiagStartLine");
Action::DiagLineStart { text } => {
info!("Caught Action::DiagLineStart {{ \"{}\" }}", &text);
self.lines.push(ProgressLine {
name: text,
text: String::new(),
text: String::from("OK"),
result: DiagResult::Pass,
running: true,
});
}
Action::DiagEndLine { result, text } => {
Action::DiagLineUpdate { result, text } => {
info!(
"Caught Action::DiagEndLine {{ {}, \"{}\" }}",
"Caught Action::DiagLineUpdate {{ {}, \"{}\" }}",
&result, &text
);
if let Some(line) = self.lines.last_mut() {
@ -88,6 +90,12 @@ impl Component for Progress {
line.text = text;
}
}
Action::DiagLineEnd { text } => {
info!("Caught Action::DiagLineEnd {{ \"{}\" }}", &text);
if let Some(line) = self.lines.last_mut() {
line.running = false;
}
}
Action::SetMode(mode) => self.mode = mode,
_ => {}
};
@ -111,7 +119,7 @@ impl Component for Progress {
DiagResult::Fail => Color::Red,
DiagResult::Warn => Color::Yellow,
};
let text = if line.text.is_empty() {
let text = if line.running || line.text.is_empty() {
String::from("...")
} else {
line.text.clone()

View file

@ -13,29 +13,9 @@
// You should have received a copy of the GNU General Public License
// along with Deja-Vu. If not, see <https://www.gnu.org/licenses/>.
use std::{collections::HashMap, fmt};
use std::collections::HashMap;
pub enum Type {
Bitlocker,
BootConfigData,
FileSystem,
Registry,
System,
Unknown,
}
impl fmt::Display for Type {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Type::Bitlocker => write!(f, "Bitlocker"),
Type::BootConfigData => write!(f, "Boot Files"),
Type::FileSystem => write!(f, "Filesystem"),
Type::Registry => write!(f, "Registry"),
Type::System => write!(f, "System Files"),
Type::Unknown => write!(f, "Unknown Type"),
}
}
}
use tracing::warn;
#[derive(Debug)]
pub struct Groups {
@ -51,8 +31,11 @@ impl Groups {
}
}
pub fn contains(&self, name: &str) -> bool {
self.items.contains_key(name)
pub fn current_group(&self) -> String {
match self.order.last() {
Some(label) => label.clone(),
None => String::from("No current group"),
}
}
pub fn get(&self) -> Vec<&Line> {
@ -65,24 +48,24 @@ impl Groups {
lines
}
pub fn update(&mut self, title: String, passed: Option<bool>, info: Option<String>) {
pub fn start(&mut self, title: String) {
self.order.push(title.clone());
self.items.insert(
title.clone(),
Line {
title,
passed: true,
info: Vec::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 {
let info_list = if info.is_some() {
vec![info.unwrap()]
} else {
Vec::new()
};
self.order.push(title.clone());
self.items.insert(
title.clone(),
Line {
title,
passed: passed.unwrap_or(true),
info: info_list,
},
);
warn!("WARNING/DELETEME - This shouldn't happen?!");
self.start(title);
}
}
}
@ -95,34 +78,8 @@ pub struct Line {
}
impl Line {
pub fn update(&mut self, passed: Option<bool>, info: Option<String>) {
if let Some(result) = passed {
self.passed &= result; // We fail if any tests in this group fail
}
if let Some(info_str) = info {
self.info.push(String::from(info_str));
}
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);
}
}
pub fn get_type(cmd_name: &str) -> Type {
if cmd_name.ends_with("exa") {
return Type::BootConfigData;
}
if cmd_name.ends_with("bcdedit.exe") {
return Type::BootConfigData;
}
if cmd_name.ends_with("dism.exe") {
return Type::System;
}
if cmd_name.ends_with("reg.exe") {
return Type::Registry;
}
if cmd_name.ends_with("chkdsk.exe") {
return Type::FileSystem;
}
if cmd_name.ends_with("manage-bde.exe") {
return Type::Bitlocker;
}
Type::Unknown
}

View file

@ -34,8 +34,9 @@ pub enum DiagResult {
pub enum Action {
// App (Boot-Diags)
BootScan,
DiagStartLine { text: String },
DiagEndLine { result: DiagResult, text: String },
DiagLineStart { text: String },
DiagLineUpdate { result: DiagResult, text: String },
DiagLineEnd { text: String },
// App (Clone)
Highlight(usize),
InstallDriver,
@ -44,7 +45,6 @@ pub enum Action {
Select(Option<usize>, Option<usize>), // indicies for (source, dest) etc
SelectRight(Option<usize>, Option<usize>), // indicies for right info pane
TaskGroupStart(String),
TaskGroupEnd,
TasksComplete,
UpdateDiskList(Vec<Disk>),
UpdateFooter(String),

View file

@ -51,7 +51,7 @@ impl FpsCounter {
#[must_use]
pub fn new() -> Self {
Self {
mode: String::from(""),
mode: String::new(),
last_tick_update: Instant::now(),
tick_count: 0,
ticks_per_second: 0.0,
@ -92,7 +92,7 @@ impl Component for FpsCounter {
fn update(&mut self, action: Action) -> Result<Option<Action>> {
match action {
Action::SetMode(mode) => {
self.mode = format!("{:?}", mode);
self.mode = format!("{mode:?}");
}
Action::Render => self.render_tick()?,
Action::Tick => self.app_tick()?,

View file

@ -166,7 +166,6 @@ pub fn get_fake_disks() -> Vec<Disk> {
letter: String::from("C"),
part_type: String::from("7"),
size: 104_857_600,
..Default::default()
},
Partition {
id: 2,
@ -181,7 +180,6 @@ pub fn get_fake_disks() -> Vec<Disk> {
letter: String::from("D"),
part_type: String::from("7"),
size: 267_701_452_800,
..Default::default()
},
Partition {
id: 6,
@ -190,7 +188,6 @@ pub fn get_fake_disks() -> Vec<Disk> {
letter: String::from("E"),
part_type: String::from("7"),
size: 524_288_000,
..Default::default()
},
],
serial: "MDZ1243".to_string(),
@ -209,7 +206,6 @@ pub fn get_fake_disks() -> Vec<Disk> {
letter: String::from("G"),
part_type: String::from("7"),
size: 249_998_951_424,
..Default::default()
}],
serial: "000010000".to_string(),
size: 250_000_000_000,
@ -261,7 +257,6 @@ pub fn get_fake_disks() -> Vec<Disk> {
letter: String::from("I"),
part_type: String::from("EFI"),
size: 209_715_200,
..Default::default()
},
Partition {
id: 2,

View file

@ -33,57 +33,57 @@ use crate::system::disk::{
static DEFAULT_MAX_DISKS: usize = 8;
struct RegexList {
re_detail_all_disks: OnceLock<Regex>,
re_detail_disk: OnceLock<Regex>,
re_detail_partition: OnceLock<Regex>,
re_disk_numbers: OnceLock<Regex>,
re_list_disk: OnceLock<Regex>,
re_list_partition: OnceLock<Regex>,
re_list_volumes: OnceLock<Regex>,
re_split_all_disks: OnceLock<Regex>,
detail_all_disks: OnceLock<Regex>,
detail_disk: OnceLock<Regex>,
detail_partition: OnceLock<Regex>,
disk_numbers: OnceLock<Regex>,
list_disk: OnceLock<Regex>,
list_partition: OnceLock<Regex>,
list_volumes: OnceLock<Regex>,
split_all_disks: OnceLock<Regex>,
re_uuid: OnceLock<Regex>,
}
impl RegexList {
pub fn detail_all_disks(&self) -> &Regex {
self.re_detail_all_disks.get_or_init(|| {
self.detail_all_disks.get_or_init(|| {
Regex::new(r"(?s)Disk (\d+) is now the selected disk.*?\r?\n\s*\r?\n(.*)").unwrap()
})
}
pub fn detail_disk(&self) -> &Regex {
self.re_detail_disk.get_or_init(|| {
self.detail_disk.get_or_init(|| {
Regex::new(r"(.*?)\r?\nDisk ID\s*:\s+(.*?)\r?\nType\s*:\s+(.*?)\r?\n").unwrap()
})
}
pub fn detail_partition(&self) -> &Regex {
self.re_detail_partition
self.detail_partition
.get_or_init(|| Regex::new(r"Partition (\d+)\r?\nType\s*: (\S+)(\r?\n.*){5}\s*(Volume.*\r?\n.*\r?\n|There is no volume)(.*)").unwrap())
}
pub fn disk_numbers(&self) -> &Regex {
self.re_disk_numbers
self.disk_numbers
.get_or_init(|| Regex::new(r"\s+Disk\s+(\d+).*\n.*\n.*\nDisk ID:").unwrap())
}
pub fn list_disk(&self) -> &Regex {
self.re_list_disk
self.list_disk
.get_or_init(|| Regex::new(r"Disk\s+(\d+)\s+(\w+)\s+(\d+\s+\w+B)").unwrap())
}
pub fn list_partition(&self) -> &Regex {
self.re_list_partition
self.list_partition
.get_or_init(|| Regex::new(r"Partition\s+(\d+)\s+\w+\s+(\d+\s+\w+B)").unwrap())
}
pub fn split_all_disks(&self) -> &Regex {
self.re_split_all_disks
self.split_all_disks
.get_or_init(|| Regex::new(r"Disk \d+ is now the selected disk").unwrap())
}
pub fn list_volumes(&self) -> &Regex {
self.re_list_volumes.get_or_init(|| {
self.list_volumes.get_or_init(|| {
// Volume ### Ltr Label Fs Type Size Status Info
// ---------- --- ----------- ----- ---------- ------- --------- --------
// * Volume 1 S ESP FAT32 Partition 100 MB Healthy Hidden
@ -100,14 +100,14 @@ impl RegexList {
}
static REGEXES: RegexList = RegexList {
re_detail_all_disks: OnceLock::new(),
re_detail_disk: OnceLock::new(),
re_detail_partition: OnceLock::new(),
re_disk_numbers: OnceLock::new(),
re_list_disk: OnceLock::new(),
re_list_partition: OnceLock::new(),
re_list_volumes: OnceLock::new(),
re_split_all_disks: OnceLock::new(),
detail_all_disks: OnceLock::new(),
detail_disk: OnceLock::new(),
detail_partition: OnceLock::new(),
disk_numbers: OnceLock::new(),
list_disk: OnceLock::new(),
list_partition: OnceLock::new(),
list_volumes: OnceLock::new(),
split_all_disks: OnceLock::new(),
re_uuid: OnceLock::new(),
};
@ -152,7 +152,7 @@ pub fn get_partition_details(
disk_details: Option<&str>,
part_details: Option<&str>,
) -> Vec<Partition> {
let re_list_partitions = REGEXES.list_partition();
let list_partitions = REGEXES.list_partition();
let mut parts = Vec::new();
// List partition
@ -163,7 +163,7 @@ pub fn get_partition_details(
let script = format!("select disk {disk_id}\r\nlist partition");
contents = run_script(&script);
};
for (_, [number, size]) in re_list_partitions
for (_, [number, size]) in list_partitions
.captures_iter(&contents)
.map(|c| c.extract())
{
@ -274,8 +274,8 @@ pub fn build_get_disk_script(disk_nums: Option<Vec<&str>>) -> String {
}
pub fn get_disks() -> Vec<Disk> {
let re_detail_all_disks = REGEXES.detail_all_disks();
let re_list_disk = REGEXES.list_disk();
let detail_all_disks = REGEXES.detail_all_disks();
let list_disk = REGEXES.list_disk();
let mut contents: String;
let mut output;
let mut script: String;
@ -317,7 +317,7 @@ pub fn get_disks() -> Vec<Disk> {
// i.e. 0, 1, 3, 4
// For instance, this can happen if a drive is disconnected after startup
let mut disks_map: HashMap<&str, Disk> = HashMap::with_capacity(DEFAULT_MAX_DISKS);
for (_, [number, _status, size]) in re_list_disk
for (_, [number, _status, size]) in list_disk
.captures_iter(dp_sections.remove(0)) // This is the "list disk" section
.map(|c| c.extract())
{
@ -334,10 +334,7 @@ pub fn get_disks() -> Vec<Disk> {
// Add Disk details
let mut disks_raw: Vec<Disk> = Vec::with_capacity(DEFAULT_MAX_DISKS);
for section in dp_sections {
for (_, [id, details]) in re_detail_all_disks
.captures_iter(section)
.map(|c| c.extract())
{
for (_, [id, details]) in detail_all_disks.captures_iter(section).map(|c| c.extract()) {
if let Some(disk) = disks_map.remove(id) {
// We remove the disk from the HashMap because we're moving it to the Vec
let mut disk = get_disk_details(disk.id, disk.size, Some(details));
@ -357,19 +354,19 @@ pub fn parse_disk_numbers(contents: &str) -> Vec<&str> {
//
//Red Hat VirtIO SCSI Disk Device
//Disk ID: {E9CE8DFA-46B2-43C1-99BB-850C661CEE6B}
let re_disk_numbers = REGEXES.disk_numbers();
let disk_numbers = REGEXES.disk_numbers();
let mut disk_nums = Vec::new();
for (_, [number]) in re_disk_numbers.captures_iter(contents).map(|c| c.extract()) {
for (_, [number]) in disk_numbers.captures_iter(contents).map(|c| c.extract()) {
disk_nums.push(number);
}
disk_nums
}
pub fn parse_partition_details(parts: &mut [Partition], contents: &str) {
let re_detail_partition = REGEXES.detail_partition();
let re_list_volume = REGEXES.list_volumes();
let detail_partition = REGEXES.detail_partition();
let list_volume = REGEXES.list_volumes();
for (part_index, (_, [_part_id, part_type, _, _vol_header, vol_line])) in re_detail_partition
for (part_index, (_, [_part_id, part_type, _, _vol_header, vol_line])) in detail_partition
.captures_iter(contents)
.map(|c| c.extract())
.enumerate()
@ -380,7 +377,7 @@ pub fn parse_partition_details(parts: &mut [Partition], contents: &str) {
// Volume info
for (_, [_id, letter, label, fs_type]) in
re_list_volume.captures_iter(vol_line).map(|c| c.extract())
list_volume.captures_iter(vol_line).map(|c| c.extract())
{
part.label = String::from(label.trim());
part.letter = String::from(letter.trim());
@ -425,11 +422,11 @@ pub fn run_script(script: &str) -> String {
pub fn split_diskpart_disk_output(contents: &str) -> Vec<&str> {
// NOTE: A simple split isn't helpful since we want to include the matching lines
let re_split_all_disks = REGEXES.split_all_disks();
let split_all_disks = REGEXES.split_all_disks();
let mut sections = Vec::new();
let mut starts: Vec<usize> = vec![0];
let mut ends: Vec<usize> = Vec::new();
let _: Vec<_> = re_split_all_disks
let _: Vec<_> = split_all_disks
.find_iter(contents)
.map(|m| {
ends.push(m.start() - 1);

View file

@ -53,7 +53,7 @@ pub enum TaskType {
UpdateDestDisk(usize), // (disk_index)
UpdateDiskList,
GroupStart { label: String },
GroupEnd,
GroupEnd { label: String },
}
impl fmt::Display for TaskType {
@ -75,7 +75,7 @@ impl fmt::Display for TaskType {
TaskType::UpdateDestDisk(_) => write!(f, "UpdateDestDisk"),
TaskType::UpdateDiskList => write!(f, "UpdateDiskList"),
TaskType::GroupStart { label } => write!(f, "GroupStart({})", &label),
TaskType::GroupEnd => write!(f, "GroupEnd"),
TaskType::GroupEnd { label } => write!(f, "GroupEnd({})", &label),
}
}
}
@ -136,10 +136,12 @@ impl Tasks {
self.task_list.push_back(Task::new(TaskType::GroupStart {
label: group_label.to_string(),
}));
group_tasks.into_iter().for_each(|task| {
for task in group_tasks {
self.task_list.push_back(Task::new(task));
});
self.task_list.push_back(Task::new(TaskType::GroupEnd));
}
self.task_list.push_back(Task::new(TaskType::GroupEnd {
label: group_label.to_string(),
}));
}
#[must_use]
@ -249,9 +251,13 @@ impl Tasks {
}));
}
TaskType::GroupStart { ref label } => {
self.action_tx.send(Action::TaskGroupStart(label.clone()))?
self.action_tx.send(Action::TaskGroupStart(label.clone()))?;
}
TaskType::GroupEnd { ref label } => {
self.action_tx.send(Action::DiagLineEnd {
text: label.clone(),
})?;
}
TaskType::GroupEnd => self.action_tx.send(Action::TaskGroupEnd)?,
}
// Done
self.cur_task.replace(task);
@ -326,22 +332,21 @@ fn test_paths(
) -> JoinHandle<()> {
thread::spawn(move || {
let mut missing_paths = Vec::new();
let task_result: TaskResult;
path_list.iter().for_each(|path| {
for path in path_list {
if !path.exists() {
missing_paths.push(String::from(path.to_string_lossy()));
}
});
}
if missing_paths.is_empty() {
let task_result = if missing_paths.is_empty() {
// No missing paths
task_result = TaskResult::Output(String::from("OK"), String::new(), true);
TaskResult::Output(String::from("OK"), String::new(), true)
} else {
task_result = TaskResult::Output(
TaskResult::Output(
String::from("Missing item(s)"),
missing_paths.join(",\n"),
false,
);
)
};
let err_str = format!("Failed to send TaskResult: {:?}", &task_result);