Add boot diagnostic sections
Still very much WIP
This commit is contained in:
parent
8f8f78a161
commit
7b0deb4cc7
8 changed files with 578 additions and 208 deletions
|
|
@ -15,7 +15,7 @@
|
||||||
//
|
//
|
||||||
use crate::diags;
|
use crate::diags;
|
||||||
use core::{
|
use core::{
|
||||||
action::Action,
|
action::{Action, DiagResult},
|
||||||
components::{
|
components::{
|
||||||
Component,
|
Component,
|
||||||
footer::Footer,
|
footer::Footer,
|
||||||
|
|
@ -39,6 +39,7 @@ use core::{
|
||||||
tui::{Event, Tui},
|
tui::{Event, Tui},
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
|
collections::VecDeque,
|
||||||
env,
|
env,
|
||||||
iter::zip,
|
iter::zip,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
|
|
@ -75,6 +76,7 @@ pub struct App {
|
||||||
selections: Vec<Option<usize>>,
|
selections: Vec<Option<usize>>,
|
||||||
system32: String,
|
system32: String,
|
||||||
tasks: Tasks,
|
tasks: Tasks,
|
||||||
|
task_groups: VecDeque<Option<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
|
|
@ -100,6 +102,8 @@ impl App {
|
||||||
Box::new(Right::new()),
|
Box::new(Right::new()),
|
||||||
Box::new(Footer::new()),
|
Box::new(Footer::new()),
|
||||||
Box::new(popup::Popup::new()),
|
Box::new(popup::Popup::new()),
|
||||||
|
Box::new(crate::components::progress::Progress::new()),
|
||||||
|
Box::new(crate::components::results::Results::new()),
|
||||||
],
|
],
|
||||||
config: Config::new()?,
|
config: Config::new()?,
|
||||||
diag_groups: diags::Groups::new(),
|
diag_groups: diags::Groups::new(),
|
||||||
|
|
@ -116,6 +120,7 @@ impl App {
|
||||||
system32: String::new(),
|
system32: String::new(),
|
||||||
selections: vec![None, None],
|
selections: vec![None, None],
|
||||||
tasks,
|
tasks,
|
||||||
|
task_groups: VecDeque::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -124,10 +129,10 @@ impl App {
|
||||||
if let Some(disk_index) = self.clone.disk_index_dest {
|
if let Some(disk_index) = self.clone.disk_index_dest {
|
||||||
let disk_list = self.clone.disk_list.lock().unwrap();
|
let disk_list = self.clone.disk_list.lock().unwrap();
|
||||||
if let Some(disk) = disk_list.get(disk_index) {
|
if let Some(disk) = disk_list.get(disk_index) {
|
||||||
if let Some(boot_index) = self.clone.part_index_boot {
|
if let Some(os_index) = self.clone.part_index_os {
|
||||||
if let Ok(task) = boot::inject_driver(
|
if let Ok(task) = boot::inject_driver(
|
||||||
driver,
|
driver,
|
||||||
disk.get_part_letter(boot_index).as_str(),
|
disk.get_part_letter(os_index).as_str(),
|
||||||
&self.system32,
|
&self.system32,
|
||||||
) {
|
) {
|
||||||
self.tasks.add(task);
|
self.tasks.add(task);
|
||||||
|
|
@ -192,122 +197,7 @@ impl App {
|
||||||
popup::Type::Info,
|
popup::Type::Info,
|
||||||
String::from("Gathering info..."),
|
String::from("Gathering info..."),
|
||||||
))?;
|
))?;
|
||||||
|
self.queue_boot_scan_tasks()?;
|
||||||
// Get System32 path
|
|
||||||
let system32 = if cfg!(windows) {
|
|
||||||
if let Ok(path) = env::var("SYSTEMROOT") {
|
|
||||||
format!("{path}\\System32")
|
|
||||||
} else {
|
|
||||||
self.action_tx.send(Action::Error(String::from(
|
|
||||||
"ERROR\n\n\nFailed to find SYSTEMROOT",
|
|
||||||
)))?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
String::from(".")
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add tasks
|
|
||||||
let disk_list = self.clone.disk_list.lock().unwrap();
|
|
||||||
if let Some(disk_index) = self.clone.disk_index_dest {
|
|
||||||
if let Some(disk) = disk_list.get(disk_index) {
|
|
||||||
let table_type = disk.part_type.clone();
|
|
||||||
let letter_boot = disk.get_part_letter(self.clone.part_index_boot.unwrap());
|
|
||||||
let letter_os = disk.get_part_letter(self.clone.part_index_os.unwrap());
|
|
||||||
|
|
||||||
// Safety check
|
|
||||||
if letter_boot.is_empty() || letter_os.is_empty() {
|
|
||||||
self.action_tx.send(Action::Error(String::from(
|
|
||||||
"ERROR\n\n\nFailed to get drive letters for the destination",
|
|
||||||
)))?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
// BCD
|
|
||||||
self.tasks.add(TaskType::CommandWait(
|
|
||||||
PathBuf::from(format!("{system32}\\bcdedit.exe")),
|
|
||||||
vec![
|
|
||||||
String::from("/store"),
|
|
||||||
format!(
|
|
||||||
"{letter_boot}:{}\\Boot\\BCD",
|
|
||||||
if table_type == PartitionTableType::Guid {
|
|
||||||
"\\EFI\\Microsoft"
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
),
|
|
||||||
String::from("/enum"),
|
|
||||||
],
|
|
||||||
));
|
|
||||||
|
|
||||||
// Bitlocker
|
|
||||||
self.tasks.add(TaskType::CommandWait(
|
|
||||||
PathBuf::from(format!("{system32}\\manage-bde.exe")),
|
|
||||||
vec![String::from("-status"), format!("{letter_os}:")],
|
|
||||||
));
|
|
||||||
self.tasks.add(TaskType::CommandWait(
|
|
||||||
PathBuf::from(format!("{system32}\\manage-bde.exe")),
|
|
||||||
vec![
|
|
||||||
String::from("-protectors"),
|
|
||||||
String::from("-get"),
|
|
||||||
format!("{letter_os}:"),
|
|
||||||
],
|
|
||||||
));
|
|
||||||
|
|
||||||
// DISM Health
|
|
||||||
self.tasks.add(TaskType::CommandWait(
|
|
||||||
PathBuf::from(format!("{system32}\\dism.exe")),
|
|
||||||
vec![format!("{letter_os}:")],
|
|
||||||
));
|
|
||||||
|
|
||||||
// Filesystem Health
|
|
||||||
self.tasks.add(TaskType::CommandWait(
|
|
||||||
PathBuf::from(format!("{system32}\\chkdsk.exe")),
|
|
||||||
vec![format!("{letter_os}:")],
|
|
||||||
));
|
|
||||||
|
|
||||||
// Registry
|
|
||||||
self.tasks.add(TaskType::CommandWait(
|
|
||||||
PathBuf::from(format!("{system32}\\reg.exe")),
|
|
||||||
vec![
|
|
||||||
String::from("load"),
|
|
||||||
String::from("HKLM\\TmpSoftware"),
|
|
||||||
format!("{letter_os}:\\Windows\\System32\\config\\SOFTWARE"),
|
|
||||||
],
|
|
||||||
));
|
|
||||||
self.tasks.add(TaskType::CommandWait(
|
|
||||||
PathBuf::from(format!("{system32}\\reg.exe")),
|
|
||||||
vec![
|
|
||||||
String::from("load"),
|
|
||||||
String::from("HKLM\\TmpSystem"),
|
|
||||||
format!("{letter_os}:\\Windows\\System32\\config\\SYSTEM"),
|
|
||||||
],
|
|
||||||
));
|
|
||||||
self.tasks.add(TaskType::CommandWait(
|
|
||||||
PathBuf::from(format!("{system32}\\reg.exe")),
|
|
||||||
vec![
|
|
||||||
String::from("load"),
|
|
||||||
String::from("HKU\\TmpDefault"),
|
|
||||||
format!("{letter_os}:\\Windows\\System32\\config\\DEFAULT"),
|
|
||||||
],
|
|
||||||
));
|
|
||||||
self.tasks.add(TaskType::CommandWait(
|
|
||||||
PathBuf::from(format!("{system32}\\reg.exe")),
|
|
||||||
vec![String::from("unload"), String::from("HKLM\\TmpSoftware")],
|
|
||||||
));
|
|
||||||
self.tasks.add(TaskType::CommandWait(
|
|
||||||
PathBuf::from(format!("{system32}\\reg.exe")),
|
|
||||||
vec![String::from("unload"), String::from("HKLM\\TmpSystem")],
|
|
||||||
));
|
|
||||||
self.tasks.add(TaskType::CommandWait(
|
|
||||||
PathBuf::from(format!("{system32}\\reg.exe")),
|
|
||||||
vec![String::from("unload"), String::from("HKU\\TmpDefault")],
|
|
||||||
));
|
|
||||||
|
|
||||||
// Files/Folders
|
|
||||||
// TODO: Check for critical folders (e.g. /Windows, /Windows/System32, etc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Mode::InjectDrivers | Mode::InstallDrivers => self.clone.scan_drivers(),
|
Mode::InjectDrivers | Mode::InstallDrivers => self.clone.scan_drivers(),
|
||||||
Mode::ScanDisks => {
|
Mode::ScanDisks => {
|
||||||
|
|
@ -510,6 +400,34 @@ impl App {
|
||||||
self.action_tx.send(build_right_items(self))?;
|
self.action_tx.send(build_right_items(self))?;
|
||||||
self.action_tx.send(Action::Select(None, None))?;
|
self.action_tx.send(Action::Select(None, None))?;
|
||||||
}
|
}
|
||||||
|
Action::TaskStart(ref task_type) => {
|
||||||
|
if self.cur_mode == Mode::BootScan {
|
||||||
|
let title: Option<String> = match task_type {
|
||||||
|
TaskType::CommandWait(cmd, _args) => {
|
||||||
|
let cmd_str = cmd.to_string_lossy().into_owned();
|
||||||
|
let diag_type = diags::get_type(&cmd_str);
|
||||||
|
Some(format!("{diag_type}"))
|
||||||
|
}
|
||||||
|
TaskType::TestPaths(_) => Some(String::from("Critical Paths")),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
if let Some(title) = title {
|
||||||
|
info!("{:?}", 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, None, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Action::TasksComplete => {
|
Action::TasksComplete => {
|
||||||
if self.cur_mode == Mode::BootDiags {
|
if self.cur_mode == Mode::BootDiags {
|
||||||
self.action_tx.send(Action::DismissPopup)?;
|
self.action_tx.send(Action::DismissPopup)?;
|
||||||
|
|
@ -536,71 +454,109 @@ impl App {
|
||||||
|
|
||||||
fn handle_task(&mut self, task: &Task) -> Result<()> {
|
fn handle_task(&mut self, task: &Task) -> Result<()> {
|
||||||
info!("Handling Task: {task:?}");
|
info!("Handling Task: {task:?}");
|
||||||
|
let title: Option<String>;
|
||||||
match self.cur_mode {
|
match self.cur_mode {
|
||||||
Mode::BootScan => {
|
Mode::BootScan => {
|
||||||
if let TaskType::CommandWait(cmd_path, _cmd_args) = &task.task_type {
|
let task_group = self.task_groups.pop_front();
|
||||||
let mut cmd_name = "";
|
match &task.task_type {
|
||||||
if let Some(path) = cmd_path.file_name() {
|
TaskType::CommandWait(cmd_path, _cmd_args) => {
|
||||||
if let Some(cmd_str) = path.to_str() {
|
let mut cmd_name = "";
|
||||||
cmd_name = cmd_str;
|
if let Some(path) = cmd_path.file_name() {
|
||||||
}
|
if let Some(cmd_str) = path.to_str() {
|
||||||
};
|
cmd_name = cmd_str;
|
||||||
let diag_title = match diags::get_type(cmd_name) {
|
|
||||||
diags::Type::BootConfigData => Some("Boot Files"),
|
|
||||||
diags::Type::Bitlocker => Some("Bitlocker"),
|
|
||||||
diags::Type::FileSystem => {
|
|
||||||
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"),
|
|
||||||
passed,
|
|
||||||
info.to_owned(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
None // Don't set title since we handle the logic here
|
};
|
||||||
}
|
let diag_type = diags::get_type(cmd_name);
|
||||||
diags::Type::Registry => Some("Registry"),
|
match diag_type {
|
||||||
diags::Type::FilesAndFolders => Some("Critical Files"),
|
diags::Type::Bitlocker
|
||||||
_ => {
|
| diags::Type::BootConfigData
|
||||||
warn!("Unrecognized command: {:?}", &cmd_path);
|
| diags::Type::Registry
|
||||||
None
|
| diags::Type::System => {
|
||||||
}
|
title = Some(format!("{diag_type}"));
|
||||||
};
|
}
|
||||||
if let Some(title) = diag_title {
|
diags::Type::FileSystem => {
|
||||||
// Just use command output
|
title = None;
|
||||||
if let Some(result) = &task.result {
|
if let Some(result) = &task.result {
|
||||||
let passed: bool;
|
let passed: bool;
|
||||||
let info: String;
|
let info: String;
|
||||||
match result {
|
match result {
|
||||||
TaskResult::Error(msg) => {
|
TaskResult::Error(msg) => {
|
||||||
passed = false;
|
passed = false;
|
||||||
info = msg.to_owned();
|
info = msg.to_owned();
|
||||||
}
|
}
|
||||||
TaskResult::Output(stdout, stderr, success) => {
|
TaskResult::Output(stdout, _stderr, success) => {
|
||||||
passed = *success;
|
passed = *success;
|
||||||
let div = if !(stdout.is_empty() || stderr.is_empty()) {
|
info = parse_chkdsk(stdout);
|
||||||
"\n\n-----------\n\n"
|
}
|
||||||
} else {
|
}
|
||||||
""
|
self.diag_groups.update(
|
||||||
};
|
String::from("Filesystem"),
|
||||||
info = format!("{stdout}{div}{stderr}");
|
Some(passed),
|
||||||
|
Some(info.to_owned()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.diag_groups
|
diags::Type::Unknown => {
|
||||||
.update(title.to_string(), passed, info.to_owned());
|
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;
|
||||||
|
let div = if !(stdout.is_empty() || stderr.is_empty()) {
|
||||||
|
"\n\n-----------\n\n"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
|
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("Maybe?"),
|
||||||
|
})?;
|
||||||
|
} else {
|
||||||
|
self.action_tx.send(Action::DiagEndLine {
|
||||||
|
result: DiagResult::Fail,
|
||||||
|
text: String::from("Nope?"),
|
||||||
|
})?;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If title was set but there wasn't a result
|
||||||
|
self.action_tx.send(Action::DiagEndLine {
|
||||||
|
result: DiagResult::Warn,
|
||||||
|
text: String::from("Yellow result?"),
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// title was not set
|
||||||
|
self.action_tx.send(Action::DiagEndLine {
|
||||||
|
result: DiagResult::Warn,
|
||||||
|
text: String::from("Yellow title?"),
|
||||||
|
})?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
|
@ -639,13 +595,156 @@ impl App {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn queue_boot_scan_tasks(&mut self) -> Result<()> {
|
||||||
|
let disk_list = self.clone.disk_list.lock().unwrap();
|
||||||
|
if let Some(disk_index) = self.clone.disk_index_dest {
|
||||||
|
if let Some(disk) = disk_list.get(disk_index) {
|
||||||
|
let table_type = disk.part_type.clone();
|
||||||
|
let letter_boot = disk.get_part_letter(self.clone.part_index_boot.unwrap());
|
||||||
|
let letter_os = disk.get_part_letter(self.clone.part_index_os.unwrap());
|
||||||
|
|
||||||
|
// Safety check
|
||||||
|
if letter_os.is_empty() {
|
||||||
|
if letter_boot.is_empty() {
|
||||||
|
self.action_tx.send(Action::Error(String::from(
|
||||||
|
"ERROR\n\n\nFailed to get drive letters for the boot and OS volumes",
|
||||||
|
)))?;
|
||||||
|
} else {
|
||||||
|
self.action_tx.send(Action::Error(String::from(
|
||||||
|
"ERROR\n\n\nFailed to get drive letter for the OS volume",
|
||||||
|
)))?;
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// BCD
|
||||||
|
if !letter_boot.is_empty() {
|
||||||
|
self.tasks.add(TaskType::CommandWait(
|
||||||
|
PathBuf::from(format!("{}\\bcdedit.exe", &self.system32)),
|
||||||
|
vec![
|
||||||
|
String::from("/store"),
|
||||||
|
format!(
|
||||||
|
"{letter_boot}:{}\\Boot\\BCD",
|
||||||
|
if table_type == PartitionTableType::Guid {
|
||||||
|
"\\EFI\\Microsoft"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
),
|
||||||
|
String::from("/enum"),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
self.task_groups
|
||||||
|
.push_back(Some(format!("{}", diags::Type::BootConfigData)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bitlocker
|
||||||
|
self.tasks.add(TaskType::CommandWait(
|
||||||
|
PathBuf::from(format!("{}\\manage-bde.exe", &self.system32)),
|
||||||
|
vec![String::from("-status"), format!("{letter_os}:")],
|
||||||
|
));
|
||||||
|
self.task_groups.push_back(None);
|
||||||
|
self.tasks.add(TaskType::CommandWait(
|
||||||
|
PathBuf::from(format!("{}\\manage-bde.exe", &self.system32)),
|
||||||
|
vec![
|
||||||
|
String::from("-protectors"),
|
||||||
|
String::from("-get"),
|
||||||
|
format!("{letter_os}:"),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
self.task_groups
|
||||||
|
.push_back(Some(format!("{}", diags::Type::Bitlocker)));
|
||||||
|
|
||||||
|
// Filesystem Health
|
||||||
|
self.tasks.add(TaskType::CommandWait(
|
||||||
|
PathBuf::from(format!("{}\\chkdsk.exe", &self.system32)),
|
||||||
|
vec![format!("{letter_os}:")],
|
||||||
|
));
|
||||||
|
self.task_groups.push_back(None);
|
||||||
|
|
||||||
|
// Files/Folders
|
||||||
|
let paths: Vec<PathBuf> = [
|
||||||
|
"Users",
|
||||||
|
"Program Files",
|
||||||
|
"Program Files (x86)",
|
||||||
|
"ProgramData",
|
||||||
|
"Windows\\System32\\config",
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.map(|s| PathBuf::from(format!("{letter_os}:\\{s}")))
|
||||||
|
.collect();
|
||||||
|
self.tasks.add(TaskType::TestPaths(paths));
|
||||||
|
self.task_groups
|
||||||
|
.push_back(Some(format!("{}", diags::Type::FileSystem)));
|
||||||
|
|
||||||
|
// DISM Health
|
||||||
|
self.tasks.add(TaskType::CommandWait(
|
||||||
|
PathBuf::from(format!("{}\\dism.exe", &self.system32)),
|
||||||
|
vec![format!("{letter_os}:")],
|
||||||
|
));
|
||||||
|
self.task_groups
|
||||||
|
.push_back(Some(format!("{}", diags::Type::System)));
|
||||||
|
|
||||||
|
// Registry
|
||||||
|
self.tasks.add(TaskType::CommandWait(
|
||||||
|
PathBuf::from(format!("{}\\reg.exe", &self.system32)),
|
||||||
|
vec![
|
||||||
|
String::from("load"),
|
||||||
|
String::from("HKLM\\TmpSoftware"),
|
||||||
|
format!("{letter_os}:\\Windows\\System32\\config\\SOFTWARE"),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
self.task_groups.push_back(None);
|
||||||
|
self.tasks.add(TaskType::CommandWait(
|
||||||
|
PathBuf::from(format!("{}\\reg.exe", &self.system32)),
|
||||||
|
vec![
|
||||||
|
String::from("load"),
|
||||||
|
String::from("HKLM\\TmpSystem"),
|
||||||
|
format!("{letter_os}:\\Windows\\System32\\config\\SYSTEM"),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
self.task_groups.push_back(None);
|
||||||
|
self.tasks.add(TaskType::CommandWait(
|
||||||
|
PathBuf::from(format!("{}\\reg.exe", &self.system32)),
|
||||||
|
vec![
|
||||||
|
String::from("load"),
|
||||||
|
String::from("HKU\\TmpDefault"),
|
||||||
|
format!("{letter_os}:\\Windows\\System32\\config\\DEFAULT"),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
self.task_groups.push_back(None);
|
||||||
|
self.tasks.add(TaskType::CommandWait(
|
||||||
|
PathBuf::from(format!("{}\\reg.exe", &self.system32)),
|
||||||
|
vec![String::from("unload"), String::from("HKLM\\TmpSoftware")],
|
||||||
|
));
|
||||||
|
self.task_groups.push_back(None);
|
||||||
|
self.tasks.add(TaskType::CommandWait(
|
||||||
|
PathBuf::from(format!("{}\\reg.exe", &self.system32)),
|
||||||
|
vec![String::from("unload"), String::from("HKLM\\TmpSystem")],
|
||||||
|
));
|
||||||
|
self.task_groups.push_back(None);
|
||||||
|
self.tasks.add(TaskType::CommandWait(
|
||||||
|
PathBuf::from(format!("{}\\reg.exe", &self.system32)),
|
||||||
|
vec![String::from("unload"), String::from("HKU\\TmpDefault")],
|
||||||
|
));
|
||||||
|
self.task_groups
|
||||||
|
.push_back(Some(format!("{}", diags::Type::Registry)));
|
||||||
|
self.tasks.add(TaskType::Sleep); // NOTE: DELETEME
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn render(&mut self, tui: &mut Tui) -> Result<()> {
|
fn render(&mut self, tui: &mut Tui) -> Result<()> {
|
||||||
tui.draw(|frame| {
|
tui.draw(|frame| {
|
||||||
if let [header, _body, footer, left, right, popup] = get_chunks(frame.area())[..] {
|
if let [header, _body, footer, left, right, popup, progress] =
|
||||||
|
get_chunks(frame.area())[..]
|
||||||
|
{
|
||||||
let component_areas = vec![
|
let component_areas = vec![
|
||||||
header, // Title Bar
|
header, // Title Bar
|
||||||
header, // FPS Counter
|
header, // FPS Counter
|
||||||
left, right, footer, popup,
|
left, right, footer, popup, // core
|
||||||
|
progress, header, // boot-diags
|
||||||
];
|
];
|
||||||
for (component, area) in zip(self.components.iter_mut(), component_areas) {
|
for (component, area) in zip(self.components.iter_mut(), component_areas) {
|
||||||
if let Err(err) = component.draw(frame, area) {
|
if let Err(err) = component.draw(frame, area) {
|
||||||
|
|
@ -710,6 +809,9 @@ fn get_chunks(r: Rect) -> Vec<Rect> {
|
||||||
// Popup
|
// Popup
|
||||||
chunks.push(centered_rect(60, 25, r));
|
chunks.push(centered_rect(60, 25, r));
|
||||||
|
|
||||||
|
// Progress
|
||||||
|
chunks.push(centered_rect(60, 80, chunks[1]));
|
||||||
|
|
||||||
// Done
|
// Done
|
||||||
chunks
|
chunks
|
||||||
}
|
}
|
||||||
|
|
@ -849,12 +951,7 @@ fn build_right_items(app: &App) -> Action {
|
||||||
let mut labels: Vec<Vec<DVLine>> = Vec::new();
|
let mut labels: Vec<Vec<DVLine>> = Vec::new();
|
||||||
let mut start_index = 0;
|
let mut start_index = 0;
|
||||||
// TODO: DELETE THIS SECTION
|
// TODO: DELETE THIS SECTION
|
||||||
start_index = 1;
|
|
||||||
items.push(vec![
|
items.push(vec![
|
||||||
DVLine {
|
|
||||||
line_parts: vec![format!("Mode: {:?}", app.cur_mode)],
|
|
||||||
line_colors: vec![Color::Reset],
|
|
||||||
},
|
|
||||||
DVLine {
|
DVLine {
|
||||||
line_parts: vec![format!(
|
line_parts: vec![format!(
|
||||||
"Parts: {:?} // {:?}",
|
"Parts: {:?} // {:?}",
|
||||||
|
|
@ -875,6 +972,7 @@ fn build_right_items(app: &App) -> Action {
|
||||||
line_colors: vec![Color::Reset],
|
line_colors: vec![Color::Reset],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
start_index += 1;
|
||||||
// TODO: DELETE THIS SECTION
|
// TODO: DELETE THIS SECTION
|
||||||
match app.cur_mode {
|
match app.cur_mode {
|
||||||
Mode::DiagMenu => {
|
Mode::DiagMenu => {
|
||||||
|
|
@ -904,10 +1002,8 @@ fn build_right_items(app: &App) -> Action {
|
||||||
|
|
||||||
// Add header
|
// Add header
|
||||||
if !header_lines.is_empty() {
|
if !header_lines.is_empty() {
|
||||||
items[0].append(&mut header_lines);
|
items.push(header_lines);
|
||||||
// TODO: Replace line above with lines below
|
start_index += 1;
|
||||||
// items.push(header_lines);
|
|
||||||
// start_index = 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -952,7 +1048,7 @@ fn build_right_items(app: &App) -> Action {
|
||||||
line_parts: vec![get_cpu_name()],
|
line_parts: vec![get_cpu_name()],
|
||||||
line_colors: vec![Color::Reset],
|
line_colors: vec![Color::Reset],
|
||||||
}]);
|
}]);
|
||||||
start_index = 2;
|
start_index += 2;
|
||||||
}
|
}
|
||||||
Mode::SelectDisks => {
|
Mode::SelectDisks => {
|
||||||
let dest_dv_line = DVLine {
|
let dest_dv_line = DVLine {
|
||||||
|
|
@ -973,7 +1069,7 @@ fn build_right_items(app: &App) -> Action {
|
||||||
}])
|
}])
|
||||||
});
|
});
|
||||||
if let Some(index) = app.clone.disk_index_dest {
|
if let Some(index) = app.clone.disk_index_dest {
|
||||||
start_index = 1;
|
start_index += 1;
|
||||||
let disk_list = app.clone.disk_list.lock().unwrap();
|
let disk_list = app.clone.disk_list.lock().unwrap();
|
||||||
if let Some(disk) = disk_list.get(index) {
|
if let Some(disk) = disk_list.get(index) {
|
||||||
// Disk Details
|
// Disk Details
|
||||||
|
|
|
||||||
2
boot_diags/src/components.rs
Normal file
2
boot_diags/src/components.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod progress;
|
||||||
|
pub mod results;
|
||||||
142
boot_diags/src/components/progress.rs
Normal file
142
boot_diags/src/components/progress.rs
Normal file
|
|
@ -0,0 +1,142 @@
|
||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
use color_eyre::Result;
|
||||||
|
use ratatui::{
|
||||||
|
Frame,
|
||||||
|
layout::Rect,
|
||||||
|
style::{Color, Style, Stylize},
|
||||||
|
text::{Line, Span},
|
||||||
|
widgets::{Block, Borders, Clear, Paragraph},
|
||||||
|
};
|
||||||
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
|
use core::{
|
||||||
|
action::{Action, DiagResult},
|
||||||
|
components::Component,
|
||||||
|
config::Config,
|
||||||
|
state::Mode,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct ProgressLine {
|
||||||
|
name: String,
|
||||||
|
text: String,
|
||||||
|
result: DiagResult,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProgressLine {
|
||||||
|
pub fn len_name(&self) -> usize {
|
||||||
|
self.name.chars().count()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len_text(&self) -> usize {
|
||||||
|
self.text.chars().count()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone)]
|
||||||
|
pub struct Progress {
|
||||||
|
command_tx: Option<UnboundedSender<Action>>,
|
||||||
|
config: Config,
|
||||||
|
lines: Vec<ProgressLine>,
|
||||||
|
mode: Mode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Progress {
|
||||||
|
#[must_use]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> {
|
||||||
|
self.command_tx = Some(tx);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_config_handler(&mut self, config: Config) -> Result<()> {
|
||||||
|
self.config = config;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for Progress {
|
||||||
|
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
||||||
|
match action {
|
||||||
|
Action::DiagStartLine { text } => {
|
||||||
|
info!("Caught Action::DiagStartLine");
|
||||||
|
self.lines.push(ProgressLine {
|
||||||
|
name: text,
|
||||||
|
text: String::new(),
|
||||||
|
result: DiagResult::Pass,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Action::DiagEndLine { result, text } => {
|
||||||
|
info!("Caught Action::DiagEndLine");
|
||||||
|
if let Some(line) = self.lines.last_mut() {
|
||||||
|
line.result = result;
|
||||||
|
line.text = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Action::SetMode(mode) => self.mode = mode,
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
|
||||||
|
if self.mode != Mode::BootScan {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut body_text = Vec::with_capacity(20); // TODO: Needs fine tuning?
|
||||||
|
body_text.push(Line::default());
|
||||||
|
|
||||||
|
// Add line(s)
|
||||||
|
self.lines.iter().for_each(|line| {
|
||||||
|
//.for_each(|(part, color)| spans.push(Span::styled(part, Style::default().fg(color))));
|
||||||
|
//Line::from(spans)
|
||||||
|
let color = match line.result {
|
||||||
|
DiagResult::Pass => Color::Green,
|
||||||
|
DiagResult::Fail => Color::Red,
|
||||||
|
DiagResult::Warn => Color::Yellow,
|
||||||
|
};
|
||||||
|
let text = if line.text.is_empty() {
|
||||||
|
String::from("...")
|
||||||
|
} else {
|
||||||
|
line.text.clone()
|
||||||
|
};
|
||||||
|
let text = format!(" [ {text} ] ");
|
||||||
|
let width = area.width as usize - line.len_name() - 1 - text.chars().count() - 7; // ' [ ]'
|
||||||
|
let spans = vec![
|
||||||
|
Span::raw(format!("{} {:.<width$}", &line.name, "")),
|
||||||
|
Span::styled(text, Style::default().fg(color)),
|
||||||
|
];
|
||||||
|
body_text.push(Line::from(spans).centered());
|
||||||
|
body_text.push(Line::default());
|
||||||
|
body_text.push(Line::default());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build block
|
||||||
|
let block = Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.style(Style::default().bold());
|
||||||
|
let body = Paragraph::new(body_text).block(block);
|
||||||
|
frame.render_widget(Clear, area);
|
||||||
|
frame.render_widget(body, area);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
53
boot_diags/src/components/results.rs
Normal file
53
boot_diags/src/components/results.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
use color_eyre::Result;
|
||||||
|
use ratatui::{Frame, layout::Rect};
|
||||||
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
|
|
||||||
|
use core::{action::Action, components::Component, config::Config};
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone)]
|
||||||
|
pub struct Results {
|
||||||
|
command_tx: Option<UnboundedSender<Action>>,
|
||||||
|
config: Config,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Results {
|
||||||
|
#[must_use]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> {
|
||||||
|
self.command_tx = Some(tx);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_config_handler(&mut self, config: Config) -> Result<()> {
|
||||||
|
self.config = config;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for Results {
|
||||||
|
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,17 +13,30 @@
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Deja-Vu. If not, see <https://www.gnu.org/licenses/>.
|
// along with Deja-Vu. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::{collections::HashMap, fmt};
|
||||||
|
|
||||||
pub enum Type {
|
pub enum Type {
|
||||||
Bitlocker,
|
Bitlocker,
|
||||||
BootConfigData,
|
BootConfigData,
|
||||||
FileSystem,
|
FileSystem,
|
||||||
FilesAndFolders,
|
|
||||||
Registry,
|
Registry,
|
||||||
|
System,
|
||||||
Unknown,
|
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"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Groups {
|
pub struct Groups {
|
||||||
items: HashMap<String, Line>,
|
items: HashMap<String, Line>,
|
||||||
|
|
@ -38,6 +51,10 @@ impl Groups {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn contains(&self, name: &str) -> bool {
|
||||||
|
self.items.contains_key(name)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get(&self) -> Vec<&Line> {
|
pub fn get(&self) -> Vec<&Line> {
|
||||||
let mut lines = Vec::new();
|
let mut lines = Vec::new();
|
||||||
self.order.iter().for_each(|key| {
|
self.order.iter().for_each(|key| {
|
||||||
|
|
@ -48,17 +65,22 @@ impl Groups {
|
||||||
lines
|
lines
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, title: String, passed: bool, info: String) {
|
pub fn update(&mut self, title: String, passed: Option<bool>, info: Option<String>) {
|
||||||
if let Some(line) = self.items.get_mut(&title) {
|
if let Some(line) = self.items.get_mut(&title) {
|
||||||
line.update(passed, info);
|
line.update(passed, info);
|
||||||
} else {
|
} else {
|
||||||
|
let info_list = if info.is_some() {
|
||||||
|
vec![info.unwrap()]
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
};
|
||||||
self.order.push(title.clone());
|
self.order.push(title.clone());
|
||||||
self.items.insert(
|
self.items.insert(
|
||||||
title.clone(),
|
title.clone(),
|
||||||
Line {
|
Line {
|
||||||
title,
|
title,
|
||||||
passed,
|
passed: passed.unwrap_or(false),
|
||||||
info: vec![info],
|
info: info_list,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -73,29 +95,33 @@ pub struct Line {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Line {
|
impl Line {
|
||||||
pub fn update(&mut self, passed: bool, info: String) {
|
pub fn update(&mut self, passed: Option<bool>, info: Option<String>) {
|
||||||
self.passed &= passed; // We fail if any tests in this group fail
|
if let Some(result) = passed {
|
||||||
self.info.push(info);
|
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 get_type(cmd_name: &str) -> Type {
|
pub fn get_type(cmd_name: &str) -> Type {
|
||||||
if cmd_name == "exa" {
|
if cmd_name.ends_with("exa") {
|
||||||
return Type::BootConfigData;
|
return Type::BootConfigData;
|
||||||
}
|
}
|
||||||
if cmd_name == "bcdedit.exe" {
|
if cmd_name.ends_with("bcdedit.exe") {
|
||||||
return Type::BootConfigData;
|
return Type::BootConfigData;
|
||||||
}
|
}
|
||||||
if cmd_name == "dir" {
|
if cmd_name.ends_with("dism.exe") {
|
||||||
return Type::FilesAndFolders;
|
return Type::System;
|
||||||
}
|
}
|
||||||
if cmd_name == "reg.exe" {
|
if cmd_name.ends_with("reg.exe") {
|
||||||
return Type::Registry;
|
return Type::Registry;
|
||||||
}
|
}
|
||||||
if cmd_name == "chkdsk.exe" {
|
if cmd_name.ends_with("chkdsk.exe") {
|
||||||
return Type::FileSystem;
|
return Type::FileSystem;
|
||||||
}
|
}
|
||||||
if cmd_name == "manage-bde.exe" {
|
if cmd_name.ends_with("manage-bde.exe") {
|
||||||
return Type::Bitlocker;
|
return Type::Bitlocker;
|
||||||
}
|
}
|
||||||
Type::Unknown
|
Type::Unknown
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ use color_eyre::Result;
|
||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
|
|
||||||
mod app;
|
mod app;
|
||||||
|
mod components;
|
||||||
mod diags;
|
mod diags;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,22 @@ use crate::{
|
||||||
line::DVLine,
|
line::DVLine,
|
||||||
state::Mode,
|
state::Mode,
|
||||||
system::disk::Disk,
|
system::disk::Disk,
|
||||||
|
tasks::TaskType,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Display, Serialize, Deserialize)]
|
||||||
|
pub enum DiagResult {
|
||||||
|
Pass,
|
||||||
|
Fail,
|
||||||
|
Warn,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Display, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Display, Serialize, Deserialize)]
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
// App (Boot-Diags)
|
// App (Boot-Diags)
|
||||||
BootScan,
|
BootScan,
|
||||||
|
DiagStartLine { text: String },
|
||||||
|
DiagEndLine { result: DiagResult, text: String },
|
||||||
// App (Clone)
|
// App (Clone)
|
||||||
Highlight(usize),
|
Highlight(usize),
|
||||||
InstallDriver,
|
InstallDriver,
|
||||||
|
|
@ -34,6 +44,7 @@ pub enum Action {
|
||||||
ScanDisks,
|
ScanDisks,
|
||||||
Select(Option<usize>, Option<usize>), // indicies for (source, dest) etc
|
Select(Option<usize>, Option<usize>), // indicies for (source, dest) etc
|
||||||
SelectRight(Option<usize>, Option<usize>), // indicies for right info pane
|
SelectRight(Option<usize>, Option<usize>), // indicies for right info pane
|
||||||
|
TaskStart(TaskType),
|
||||||
TasksComplete,
|
TasksComplete,
|
||||||
UpdateDiskList(Vec<Disk>),
|
UpdateDiskList(Vec<Disk>),
|
||||||
UpdateFooter(String),
|
UpdateFooter(String),
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
|
|
@ -41,13 +42,14 @@ pub enum TaskResult {
|
||||||
Output(String, String, bool), // stdout, stderr, success
|
Output(String, String, bool), // stdout, stderr, success
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
pub enum TaskType {
|
pub enum TaskType {
|
||||||
CommandNoWait(PathBuf, Vec<String>), // (command, args)
|
CommandNoWait(PathBuf, Vec<String>), // (command, args)
|
||||||
CommandWait(PathBuf, Vec<String>), // (command, args)
|
CommandWait(PathBuf, Vec<String>), // (command, args)
|
||||||
Diskpart(String), // (script_as_string)
|
Diskpart(String), // (script_as_string)
|
||||||
ScanDisks,
|
ScanDisks,
|
||||||
Sleep,
|
Sleep,
|
||||||
|
TestPaths(Vec<PathBuf>),
|
||||||
UpdateDestDisk(usize), // (disk_index)
|
UpdateDestDisk(usize), // (disk_index)
|
||||||
UpdateDiskList,
|
UpdateDiskList,
|
||||||
}
|
}
|
||||||
|
|
@ -67,6 +69,7 @@ impl fmt::Display for TaskType {
|
||||||
}
|
}
|
||||||
TaskType::ScanDisks => write!(f, "ScanDisks"),
|
TaskType::ScanDisks => write!(f, "ScanDisks"),
|
||||||
TaskType::Sleep => write!(f, "Sleep"),
|
TaskType::Sleep => write!(f, "Sleep"),
|
||||||
|
TaskType::TestPaths(_) => write!(f, "TestPaths"),
|
||||||
TaskType::UpdateDestDisk(_) => write!(f, "UpdateDestDisk"),
|
TaskType::UpdateDestDisk(_) => write!(f, "UpdateDestDisk"),
|
||||||
TaskType::UpdateDiskList => write!(f, "UpdateDiskList"),
|
TaskType::UpdateDiskList => write!(f, "UpdateDiskList"),
|
||||||
}
|
}
|
||||||
|
|
@ -170,6 +173,8 @@ impl Tasks {
|
||||||
self.cur_task = self.task_list.pop_front();
|
self.cur_task = self.task_list.pop_front();
|
||||||
if let Some(task) = self.cur_task.take() {
|
if let Some(task) = self.cur_task.take() {
|
||||||
let task_tx = self.task_tx.clone();
|
let task_tx = self.task_tx.clone();
|
||||||
|
self.action_tx
|
||||||
|
.send(Action::TaskStart(task.task_type.clone()))?;
|
||||||
match task.task_type {
|
match task.task_type {
|
||||||
TaskType::CommandNoWait(ref cmd_path, ref cmd_args) => {
|
TaskType::CommandNoWait(ref cmd_path, ref cmd_args) => {
|
||||||
self.cur_handle = None;
|
self.cur_handle = None;
|
||||||
|
|
@ -199,6 +204,9 @@ impl Tasks {
|
||||||
TaskType::Sleep => {
|
TaskType::Sleep => {
|
||||||
self.cur_handle = Some(thread::spawn(|| sleep(Duration::from_millis(250))));
|
self.cur_handle = Some(thread::spawn(|| sleep(Duration::from_millis(250))));
|
||||||
}
|
}
|
||||||
|
TaskType::TestPaths(ref list) => {
|
||||||
|
self.cur_handle = Some(test_paths(list.clone(), task_tx.clone()));
|
||||||
|
}
|
||||||
TaskType::UpdateDestDisk(index) => {
|
TaskType::UpdateDestDisk(index) => {
|
||||||
self.action_tx.send(Action::DisplayPopup(
|
self.action_tx.send(Action::DisplayPopup(
|
||||||
popup::Type::Info,
|
popup::Type::Info,
|
||||||
|
|
@ -272,7 +280,7 @@ fn run_task_command(
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// Simulate task if not running under Windows
|
// Simulate task if not running under Windows
|
||||||
thread::spawn(|| sleep(Duration::from_millis(250)))
|
thread::spawn(|| sleep(Duration::from_millis(500)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -294,3 +302,34 @@ fn run_task_diskpart(script: &str, task_tx: mpsc::UnboundedSender<TaskResult>) -
|
||||||
thread::spawn(|| sleep(Duration::from_millis(250)))
|
thread::spawn(|| sleep(Duration::from_millis(250)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test_paths(
|
||||||
|
path_list: Vec<PathBuf>,
|
||||||
|
task_tx: mpsc::UnboundedSender<TaskResult>,
|
||||||
|
) -> JoinHandle<()> {
|
||||||
|
thread::spawn(move || {
|
||||||
|
let mut missing_paths = Vec::new();
|
||||||
|
let task_result: TaskResult;
|
||||||
|
path_list.iter().for_each(|path| {
|
||||||
|
if !path.exists() {
|
||||||
|
missing_paths.push(String::from(path.to_string_lossy()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if missing_paths.is_empty() {
|
||||||
|
// No missing paths
|
||||||
|
task_result = TaskResult::Output(String::from("OK"), String::new(), true);
|
||||||
|
} else {
|
||||||
|
task_result = TaskResult::Output(
|
||||||
|
String::from("Missing item(s)"),
|
||||||
|
missing_paths.join(",\n"),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
let err_str = format!("Failed to send TaskResult: {:?}", &task_result);
|
||||||
|
task_tx
|
||||||
|
.send(task_result)
|
||||||
|
.unwrap_or_else(|_| panic!("{}", err_str));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue