Add option to include local backup WIMs

This commit is contained in:
2Shirt 2025-11-29 05:41:08 -08:00
parent dd4733c991
commit 09b204c0b0
Signed by: 2Shirt
GPG key ID: 152FAC923B0E132C
5 changed files with 101 additions and 31 deletions

View file

@ -190,6 +190,7 @@
},
"ScanWinSources": {
"<Enter>": "Process",
"<b>": "FindWimBackups",
"<n>": "FindWimNetwork",
"<q>": "Quit",
"<Ctrl-d>": "Quit",
@ -200,6 +201,7 @@
"<Enter>": "Process",
"<Up>": "KeyUp",
"<Down>": "KeyDown",
"<b>": "PrevScreen",
"<q>": "Quit",
"<Ctrl-d>": "Quit",
"<Ctrl-c>": "Quit",

View file

@ -56,6 +56,7 @@ pub enum Action {
Restart,
Shutdown,
// App (Win-Installer)
FindWimBackups,
FindWimNetwork,
SetUserName(String),
// Screens

View file

@ -47,8 +47,7 @@ use tracing::{debug, info};
use crate::{
components::{set_username::InputUsername, wim_scan::WimScan},
state::State,
wim::WimImage,
state::{ScanType, State},
};
pub struct App {
@ -110,7 +109,19 @@ impl App {
Mode::SelectTableType => Mode::ScanWinSources,
Mode::ScanWinSources => Mode::SelectWinSource,
Mode::SelectWinSource => Mode::SelectWinImage,
Mode::SelectWinImage => Mode::SetUserName,
Mode::SelectWinImage => {
let mut next_mode = Mode::SetUserName;
// TODO: FIXME - Race condition?
// if let Ok(wim_sources) = self.state.wim_sources.lock()
// && let Some(index) = self.state.wim_image_index
// {
// let image = wim_sources.get_file(index);
// if !image.is_installer {
// next_mode = Mode::Confirm;
// }
// }
next_mode
}
Mode::SetUserName => Mode::Confirm,
Mode::Confirm => Mode::Process, // i.e. format, apply, etc
Mode::Process | Mode::Done => Mode::Done,
@ -150,7 +161,7 @@ impl App {
String::from("Scanning Disks..."),
))?;
}
Mode::ScanWinSources => self.state.scan_wim_local(),
Mode::ScanWinSources => self.state.scan_wim_local(ScanType::WindowsInstallers),
Mode::Done => {
self.action_tx
.send(Action::DisplayPopup(popup::Type::Success, popup::fortune()))?;
@ -289,6 +300,10 @@ impl App {
Action::InstallDriver => {
self.action_tx.send(Action::SetMode(Mode::InstallDrivers))?;
}
Action::FindWimBackups => {
self.state.reset_local();
self.state.scan_wim_local(ScanType::GeneralWimFiles);
}
Action::FindWimNetwork => {
self.state.reset_network();
self.state.scan_wim_network();
@ -302,7 +317,10 @@ impl App {
Mode::SelectTableType => {
self.action_tx.send(Action::SetMode(Mode::SelectDisks))?;
}
Mode::SetUserName => {
Mode::SelectWinSource => {
self.action_tx.send(Action::SetMode(Mode::ScanWinSources))?;
}
Mode::SelectWinImage | Mode::SetUserName | Mode::Confirm => {
self.action_tx
.send(Action::SetMode(Mode::SelectWinSource))?;
}
@ -413,9 +431,9 @@ fn build_footer_string(cur_mode: Mode) -> String {
Mode::SelectWinSource | Mode::SelectWinImage => {
String::from("(Enter) to select / (q) to quit")
}
Mode::ScanWinSources => {
String::from("(Enter) to continue / (n) to scan network / (q) to quit")
}
Mode::ScanWinSources => String::from(
"(Enter) to continue / (b) to scan for backups / (n) to scan network / (q) to quit",
),
Mode::SetUserName => String::from("(Enter) to continue / (Esc) to go back"),
Mode::Confirm => String::from("(Enter) to confirm / (b) to go back / (q) to quit"),
Mode::Done | Mode::Failed | Mode::Process => String::from("(Enter) or (q) to quit"),
@ -633,11 +651,11 @@ fn build_right_items(app: &App) -> Action {
}
Mode::SelectWinImage | Mode::SetUserName | Mode::Confirm => {
info!("Building right items for: {:?}", &app.cur_mode);
let source;
let wim_file;
if let Ok(wim_sources) = app.state.wim_sources.lock()
&& let Some(index) = app.state.wim_file_index
{
source = wim_sources.get_file(index);
wim_file = wim_sources.get_file(index);
} else {
panic!("Failed to get source WIM file");
}
@ -675,7 +693,7 @@ fn build_right_items(app: &App) -> Action {
line_colors: vec![Color::Cyan],
},
DVLine {
line_parts: vec![source.path.clone()],
line_parts: vec![wim_file.path.clone()],
line_colors: vec![Color::Reset],
},
DVLine::blank(),
@ -688,7 +706,7 @@ fn build_right_items(app: &App) -> Action {
// WIM Info
match app.cur_mode {
Mode::SelectWinImage => {
source.images.iter().for_each(|image| {
wim_file.images.iter().for_each(|image| {
items.push(vec![DVLine {
line_parts: vec![format!("{image}")],
line_colors: vec![Color::Reset],
@ -697,7 +715,7 @@ fn build_right_items(app: &App) -> Action {
}
Mode::Confirm => {
if let Some(index) = app.state.wim_image_index
&& let Some(image) = source.images.get(index)
&& let Some(image) = wim_file.images.get(index)
{
label_dv_lines.append(&mut vec![
DVLine {
@ -707,7 +725,9 @@ fn build_right_items(app: &App) -> Action {
DVLine::blank(),
]);
}
if let Some(username) = &app.state.username {
if wim_file.is_installer
&& let Some(username) = &app.state.username
{
label_dv_lines.append(&mut vec![DVLine {
line_parts: vec![String::from("Username: "), username.clone()],
line_colors: vec![Color::Green, Color::Reset],
@ -719,7 +739,6 @@ fn build_right_items(app: &App) -> Action {
}
// Done
info!("label_dv_lines: {:?}", &label_dv_lines);
labels.push(label_dv_lines);
}
Mode::SelectTableType => {

View file

@ -20,8 +20,6 @@ use std::{
};
use core::{
action::Action,
components::popup::Type as PopupType,
config::Config,
system::{
disk::{Disk, PartitionTableType},
@ -33,9 +31,14 @@ use tracing::info;
use crate::{
net::connect_network_share,
wim::{WimSources, parse_wim_file},
wim::{WimFile, WimSources, parse_wim_file},
};
pub enum ScanType {
GeneralWimFiles, // Includes Windows installer WIMs
WindowsInstallers,
}
#[derive(Debug, Default)]
pub struct State {
pub config: Config,
@ -70,6 +73,12 @@ impl State {
}
}
pub fn reset_local(&mut self) {
if let Ok(mut sources) = self.wim_sources.lock() {
sources.reset_local();
}
}
pub fn reset_network(&mut self) {
if let Ok(mut sources) = self.wim_sources.lock() {
sources.reset_network();
@ -80,11 +89,11 @@ impl State {
self.driver_list = drivers::scan();
}
pub fn scan_wim_local(&mut self) {
pub fn scan_wim_local(&mut self, scan_type: ScanType) {
let disk_list_arc = self.disk_list.clone();
let wim_sources_arc = self.wim_sources.clone();
tokio::task::spawn(async move {
scan_local_drives(disk_list_arc, wim_sources_arc);
scan_local_drives(disk_list_arc, wim_sources_arc, scan_type);
});
}
@ -97,18 +106,39 @@ impl State {
}
}
fn get_subfolders(path_str: &str) -> Vec<String> {
if let Ok(read_dir) = read_dir(path_str) {
read_dir
.filter_map(|item| item.ok())
.map(|item| item.path().to_string_lossy().into_owned())
.collect()
} else {
// TODO: Use better error handling here?
Vec::new()
}
}
pub fn scan_local_drives(
disk_list_arc: Arc<Mutex<Vec<Disk>>>,
wim_sources_arc: Arc<Mutex<WimSources>>,
scan_type: ScanType,
) {
let mut to_check = vec![String::from(".")];
let mut wim_files: Vec<WimFile> = Vec::new();
// Get drive letters
if let Ok(disk_list) = disk_list_arc.lock() {
disk_list.iter().for_each(|d| {
d.parts.iter().for_each(|p| {
if !p.letter.is_empty() {
to_check.push(format!("{}:\\Images", &p.letter));
match scan_type {
ScanType::GeneralWimFiles => {
to_check.append(&mut get_subfolders(&format!("{}:\\", &p.letter)));
}
ScanType::WindowsInstallers => {
to_check.push(format!("{}:\\Images", &p.letter));
}
}
}
});
})
@ -116,20 +146,28 @@ pub fn scan_local_drives(
// Scan drives
to_check.iter().for_each(|scan_path| {
let installer = scan_path.ends_with("\\Images");
info!("Scanning: {}", &scan_path);
if let Ok(read_dir) = read_dir(scan_path) {
read_dir.for_each(|item| {
if let Ok(item) = item
&& item.file_name().to_string_lossy().ends_with(".wim")
&& let Some(path_str) = item.path().to_str()
&& let Ok(new_source) = parse_wim_file(path_str)
&& let Ok(mut wim_sources) = wim_sources_arc.lock()
&& let Ok(new_source) = parse_wim_file(path_str, installer)
{
wim_sources.add_local(new_source);
wim_files.push(new_source);
}
});
}
});
// Done
wim_files.sort();
if let Ok(mut wim_sources) = wim_sources_arc.lock() {
wim_files
.into_iter()
.for_each(|file| wim_sources.add_local(file));
}
}
pub fn scan_network_share(config: Config, wim_sources_arc: Arc<Mutex<WimSources>>) {
@ -139,6 +177,7 @@ pub fn scan_network_share(config: Config, wim_sources_arc: Arc<Mutex<WimSources>
&config.network_user,
&config.network_pass,
);
let mut wim_files: Vec<WimFile> = Vec::new();
// Connect to share
if result.is_err() {
@ -152,14 +191,19 @@ pub fn scan_network_share(config: Config, wim_sources_arc: Arc<Mutex<WimSources>
if let Ok(item) = item
&& item.file_name().to_string_lossy().ends_with(".wim")
&& let Some(path_str) = item.path().to_str()
&& let Ok(new_source) = parse_wim_file(path_str)
&& let Ok(mut wim_sources) = wim_sources_arc.lock()
&& let Ok(new_source) = parse_wim_file(path_str, true)
// Assuming all network sources are installers
{
wim_sources.add_network(new_source);
wim_files.push(new_source);
}
});
}
// Done
let _ = 14;
wim_files.sort();
if let Ok(mut wim_sources) = wim_sources_arc.lock() {
wim_files
.into_iter()
.for_each(|file| wim_sources.add_network(file));
}
}

View file

@ -66,6 +66,7 @@ static WIN_BUILDS: LazyLock<HashMap<&str, &str>> = LazyLock::new(|| {
pub struct WimFile {
pub path: String,
pub images: Vec<WimImage>,
pub is_installer: bool,
}
impl WimFile {
@ -154,12 +155,10 @@ impl WimSources {
pub fn add_local(&mut self, wim_file: WimFile) {
self.local.push(wim_file);
self.local.sort();
}
pub fn add_network(&mut self, wim_file: WimFile) {
self.network.push(wim_file);
self.network.sort();
}
pub fn get_file(&self, index: usize) -> WimFile {
@ -183,6 +182,10 @@ impl WimSources {
self.network.clear();
}
pub fn reset_local(&mut self) {
self.local.clear();
}
pub fn reset_network(&mut self) {
self.network.clear();
}
@ -203,7 +206,7 @@ fn get_wim_xml(wim_file: &str) -> std::io::Result<File> {
Ok(file)
}
pub fn parse_wim_file(wim_file: &str) -> std::io::Result<WimFile> {
pub fn parse_wim_file(wim_file: &str, installer: bool) -> std::io::Result<WimFile> {
let mut wim_images: Vec<WimImage> = Vec::new();
if !Path::new(wim_file).exists() {
return Err(std::io::Error::new(
@ -268,6 +271,7 @@ pub fn parse_wim_file(wim_file: &str) -> std::io::Result<WimFile> {
let wim_file = WimFile {
path: wim_file.to_string(),
images: wim_images,
is_installer: installer,
};
Ok(wim_file)