Compare commits
No commits in common. "09b204c0b0531c0b4b84749d73b18e6b8d57405c" and "2aed8d130b1ea2e80f69d23c670d26ffe01f95e4" have entirely different histories.
09b204c0b0
...
2aed8d130b
17 changed files with 134 additions and 767 deletions
31
Cargo.lock
generated
31
Cargo.lock
generated
|
|
@ -689,7 +689,7 @@ dependencies = [
|
|||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2669,7 +2669,7 @@ dependencies = [
|
|||
"getrandom 0.3.2",
|
||||
"once_cell",
|
||||
"rustix 1.0.3",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2933,16 +2933,6 @@ version = "0.1.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc"
|
||||
|
||||
[[package]]
|
||||
name = "tui-input"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "911e93158bf80bbc94bad533b2b16e3d711e1132d69a6a6980c3920a63422c19"
|
||||
dependencies = [
|
||||
"ratatui",
|
||||
"unicode-width 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.18.0"
|
||||
|
|
@ -3154,9 +3144,7 @@ dependencies = [
|
|||
"tracing",
|
||||
"tracing-error",
|
||||
"tracing-subscriber",
|
||||
"tui-input",
|
||||
"vergen-gix",
|
||||
"windows-sys 0.61.2",
|
||||
"xml",
|
||||
]
|
||||
|
||||
|
|
@ -3210,12 +3198,6 @@ dependencies = [
|
|||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
|
|
@ -3234,15 +3216,6 @@ dependencies = [
|
|||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
|
|
|
|||
|
|
@ -17,6 +17,3 @@
|
|||
members = ["core", "boot_diags", "deja_vu", "pe_menu", "win_installer"]
|
||||
default-members = ["core", "boot_diags", "deja_vu", "pe_menu", "win_installer"]
|
||||
resolver = "2"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
|
|
|||
|
|
@ -657,9 +657,8 @@ fn build_footer_string(cur_mode: Mode) -> String {
|
|||
| Mode::PEMenu
|
||||
| Mode::PreClone
|
||||
| Mode::PostClone
|
||||
| Mode::ScanWinSources
|
||||
| Mode::ScanWinImages
|
||||
| Mode::SelectTableType
|
||||
| Mode::SelectWinSource
|
||||
| Mode::SelectWinImage
|
||||
| Mode::SetUserName => {
|
||||
panic!("This shouldn't happen?")
|
||||
|
|
@ -775,8 +774,7 @@ fn build_left_items(app: &App) -> Action {
|
|||
| Mode::PreClone
|
||||
| Mode::Clone
|
||||
| Mode::PostClone
|
||||
| Mode::ScanWinSources
|
||||
| Mode::SelectWinSource
|
||||
| Mode::ScanWinImages
|
||||
| Mode::SelectWinImage
|
||||
| Mode::SetUserName => {
|
||||
panic!("This shouldn't happen?")
|
||||
|
|
|
|||
|
|
@ -188,25 +188,14 @@
|
|||
"<Ctrl-c>": "Quit",
|
||||
"<Ctrl-z>": "Suspend"
|
||||
},
|
||||
"ScanWinSources": {
|
||||
"ScanWinImages": {
|
||||
"<Enter>": "Process",
|
||||
"<b>": "FindWimBackups",
|
||||
"<n>": "FindWimNetwork",
|
||||
"<q>": "Quit",
|
||||
"<Ctrl-d>": "Quit",
|
||||
"<Ctrl-c>": "Quit",
|
||||
"<Ctrl-z>": "Suspend"
|
||||
},
|
||||
"SelectWinSource": {
|
||||
"<Enter>": "Process",
|
||||
"<Up>": "KeyUp",
|
||||
"<Down>": "KeyDown",
|
||||
"<b>": "PrevScreen",
|
||||
"<q>": "Quit",
|
||||
"<Ctrl-d>": "Quit",
|
||||
"<Ctrl-c>": "Quit",
|
||||
"<Ctrl-z>": "Suspend"
|
||||
},
|
||||
"SelectWinImage": {
|
||||
"<Enter>": "Process",
|
||||
"<Up>": "KeyUp",
|
||||
|
|
@ -217,14 +206,11 @@
|
|||
"<Ctrl-z>": "Suspend"
|
||||
},
|
||||
"SetUserName": {
|
||||
"<Enter>": "Process",
|
||||
"<Esc>": "PrevScreen",
|
||||
"<Ctrl-d>": "Quit",
|
||||
"<Ctrl-c>": "Quit",
|
||||
"<Ctrl-z>": "Suspend"
|
||||
},
|
||||
},
|
||||
"network_server": "SERVER",
|
||||
"network_share": "SHARE",
|
||||
"network_user": "USER",
|
||||
"network_pass": "PASS"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,9 +56,7 @@ pub enum Action {
|
|||
Restart,
|
||||
Shutdown,
|
||||
// App (Win-Installer)
|
||||
FindWimBackups,
|
||||
FindWimNetwork,
|
||||
SetUserName(String),
|
||||
// Screens
|
||||
DismissPopup,
|
||||
DisplayPopup(PopupType, String),
|
||||
|
|
|
|||
|
|
@ -54,14 +54,6 @@ pub struct Config {
|
|||
pub keybindings: KeyBindings,
|
||||
#[serde(default)]
|
||||
pub styles: Styles,
|
||||
#[serde(default)]
|
||||
pub network_server: String,
|
||||
#[serde(default)]
|
||||
pub network_share: String,
|
||||
#[serde(default)]
|
||||
pub network_user: String,
|
||||
#[serde(default)]
|
||||
pub network_pass: String,
|
||||
}
|
||||
|
||||
pub static PROJECT_NAME: &str = "DEJA-VU";
|
||||
|
|
@ -84,14 +76,16 @@ impl Config {
|
|||
let config_dir = get_config_dir();
|
||||
let mut builder = config::Config::builder()
|
||||
.set_default("app_title", default_config.app_title.as_str())?
|
||||
.set_default("clone_app_path", default_config.app_title.as_str())?
|
||||
.set_default("conemu_path", default_config.app_title.as_str())?
|
||||
.set_default(
|
||||
"clone_app_path",
|
||||
String::from("C:\\Program Files\\Some Clone Tool\\app.exe"),
|
||||
)?
|
||||
.set_default(
|
||||
"conemu_path",
|
||||
String::from("C:\\Program Files\\ConEmu\\ConEmu64.exe"),
|
||||
)?
|
||||
.set_default("config_dir", config_dir.to_str().unwrap())?
|
||||
.set_default("data_dir", data_dir.to_str().unwrap())?
|
||||
.set_default("network_server", default_config.app_title.as_str())?
|
||||
.set_default("network_share", default_config.app_title.as_str())?
|
||||
.set_default("network_user", default_config.app_title.as_str())?
|
||||
.set_default("network_pass", default_config.app_title.as_str())?;
|
||||
.set_default("data_dir", data_dir.to_str().unwrap())?;
|
||||
|
||||
let config_files = [
|
||||
("config.json5", config::FileFormat::Json5),
|
||||
|
|
|
|||
|
|
@ -43,8 +43,7 @@ pub enum Mode {
|
|||
SelectParts,
|
||||
PostClone,
|
||||
// Windows Installer
|
||||
ScanWinSources,
|
||||
SelectWinSource,
|
||||
ScanWinImages,
|
||||
SelectWinImage,
|
||||
SetUserName,
|
||||
// WinPE
|
||||
|
|
|
|||
|
|
@ -129,8 +129,7 @@ impl App {
|
|||
| Mode::LogView
|
||||
| Mode::PEMenu
|
||||
| Mode::Process
|
||||
| Mode::ScanWinSources
|
||||
| Mode::SelectWinSource
|
||||
| Mode::ScanWinImages
|
||||
| Mode::SelectWinImage
|
||||
| Mode::SetBootMode
|
||||
| Mode::SetUserName => panic!("This shouldn't happen?"),
|
||||
|
|
@ -158,8 +157,7 @@ impl App {
|
|||
| Mode::LogView
|
||||
| Mode::PEMenu
|
||||
| Mode::Process
|
||||
| Mode::ScanWinSources
|
||||
| Mode::SelectWinSource
|
||||
| Mode::ScanWinImages
|
||||
| Mode::SelectWinImage
|
||||
| Mode::SetBootMode
|
||||
| Mode::SetUserName => panic!("This shouldn't happen?"),
|
||||
|
|
@ -641,8 +639,7 @@ fn build_footer_string(cur_mode: Mode) -> String {
|
|||
| Mode::LogView
|
||||
| Mode::PEMenu
|
||||
| Mode::Process
|
||||
| Mode::ScanWinSources
|
||||
| Mode::SelectWinSource
|
||||
| Mode::ScanWinImages
|
||||
| Mode::SelectWinImage
|
||||
| Mode::SetBootMode
|
||||
| Mode::SetUserName => panic!("This shouldn't happen?"),
|
||||
|
|
@ -718,8 +715,7 @@ fn build_left_items(app: &App) -> Action {
|
|||
| Mode::LogView
|
||||
| Mode::PEMenu
|
||||
| Mode::Process
|
||||
| Mode::ScanWinSources
|
||||
| Mode::SelectWinSource
|
||||
| Mode::ScanWinImages
|
||||
| Mode::SelectWinImage
|
||||
| Mode::SetBootMode
|
||||
| Mode::SetUserName => panic!("This shouldn't happen?"),
|
||||
|
|
|
|||
|
|
@ -41,9 +41,7 @@ tracing = "0.1.41"
|
|||
tracing-error = "0.2.0"
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter", "serde"] }
|
||||
tempfile = "3.23.0"
|
||||
windows-sys = { version = "0.61.1", features = ["Win32_NetworkManagement_WNet"] }
|
||||
xml = "1.1.0"
|
||||
tui-input = "0.14.0"
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = "1.0.86"
|
||||
|
|
|
|||
|
|
@ -45,10 +45,7 @@ use ratatui::{
|
|||
use tokio::sync::mpsc;
|
||||
use tracing::{debug, info};
|
||||
|
||||
use crate::{
|
||||
components::{set_username::InputUsername, wim_scan::WimScan},
|
||||
state::{ScanType, State},
|
||||
};
|
||||
use crate::{components::wim_scan::WimScan, state::State, wim::scan_local_drives};
|
||||
|
||||
pub struct App {
|
||||
// TUI
|
||||
|
|
@ -70,10 +67,9 @@ pub struct App {
|
|||
impl App {
|
||||
pub fn new(tick_rate: f64, frame_rate: f64) -> Result<Self> {
|
||||
let (action_tx, action_rx) = mpsc::unbounded_channel();
|
||||
let config = Config::new()?;
|
||||
let disk_list_arc = Arc::new(Mutex::new(Vec::new()));
|
||||
let tasks = Tasks::new(action_tx.clone(), disk_list_arc.clone());
|
||||
let state = State::new(config.clone(), disk_list_arc);
|
||||
let state = State::new(disk_list_arc);
|
||||
let wim_sources = Arc::clone(&state.wim_sources);
|
||||
Ok(Self {
|
||||
// TUI
|
||||
|
|
@ -84,11 +80,10 @@ impl App {
|
|||
Box::new(Left::new()),
|
||||
Box::new(Right::new()),
|
||||
Box::new(WimScan::new(wim_sources)),
|
||||
Box::new(InputUsername::new()),
|
||||
Box::new(Footer::new()),
|
||||
Box::new(popup::Popup::new()),
|
||||
],
|
||||
config,
|
||||
config: Config::new()?,
|
||||
frame_rate,
|
||||
last_tick_key_events: Vec::new(),
|
||||
should_quit: false,
|
||||
|
|
@ -106,22 +101,9 @@ impl App {
|
|||
Mode::Home | Mode::InstallDrivers => Mode::ScanDisks,
|
||||
Mode::ScanDisks => Mode::SelectDisks,
|
||||
Mode::SelectDisks => Mode::SelectTableType,
|
||||
Mode::SelectTableType => Mode::ScanWinSources,
|
||||
Mode::ScanWinSources => Mode::SelectWinSource,
|
||||
Mode::SelectWinSource => Mode::SelectWinImage,
|
||||
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::SelectTableType => Mode::ScanWinImages,
|
||||
Mode::ScanWinImages => Mode::SelectWinImage,
|
||||
Mode::SelectWinImage => Mode::SetUserName,
|
||||
Mode::SetUserName => Mode::Confirm,
|
||||
Mode::Confirm => Mode::Process, // i.e. format, apply, etc
|
||||
Mode::Process | Mode::Done => Mode::Done,
|
||||
|
|
@ -161,7 +143,13 @@ impl App {
|
|||
String::from("Scanning Disks..."),
|
||||
))?;
|
||||
}
|
||||
Mode::ScanWinSources => self.state.scan_wim_local(ScanType::WindowsInstallers),
|
||||
Mode::ScanWinImages => {
|
||||
let disk_list_arc = self.state.disk_list.clone();
|
||||
let wim_sources_arc = self.state.wim_sources.clone();
|
||||
tokio::task::spawn(async move {
|
||||
scan_local_drives(disk_list_arc, wim_sources_arc);
|
||||
});
|
||||
}
|
||||
Mode::Done => {
|
||||
self.action_tx
|
||||
.send(Action::DisplayPopup(popup::Type::Success, popup::fortune()))?;
|
||||
|
|
@ -300,13 +288,9 @@ 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();
|
||||
// TODO: Actually scan network!
|
||||
}
|
||||
Action::NextScreen => {
|
||||
let next_mode = self.next_mode();
|
||||
|
|
@ -317,17 +301,13 @@ impl App {
|
|||
Mode::SelectTableType => {
|
||||
self.action_tx.send(Action::SetMode(Mode::SelectDisks))?;
|
||||
}
|
||||
Mode::SelectWinSource => {
|
||||
self.action_tx.send(Action::SetMode(Mode::ScanWinSources))?;
|
||||
}
|
||||
Mode::SelectWinImage | Mode::SetUserName | Mode::Confirm => {
|
||||
self.action_tx
|
||||
.send(Action::SetMode(Mode::SelectWinSource))?;
|
||||
Mode::SetUserName => {
|
||||
self.action_tx.send(Action::SetMode(Mode::SelectWinImage))?;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Action::Process => match self.cur_mode {
|
||||
Mode::Confirm | Mode::ScanWinSources => {
|
||||
Mode::Confirm | Mode::ScanWinImages => {
|
||||
self.action_tx.send(Action::NextScreen)?;
|
||||
}
|
||||
Mode::Done => {
|
||||
|
|
@ -363,11 +343,11 @@ impl App {
|
|||
}
|
||||
}
|
||||
}
|
||||
Mode::SelectWinSource => {
|
||||
self.state.wim_file_index = one;
|
||||
}
|
||||
Mode::SelectWinImage => {
|
||||
self.state.wim_image_index = one;
|
||||
// TODO: FIXME
|
||||
// PLAN: Abuse Action::Select to send (file_index, image_index) to set all at once
|
||||
// self.state.wim_file_index = TODO;
|
||||
// self.state.wim_image_index = TODO;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
|
|
@ -379,10 +359,6 @@ impl App {
|
|||
self.action_tx.send(build_right_items(self))?;
|
||||
self.action_tx.send(Action::Select(None, None))?;
|
||||
}
|
||||
Action::SetUserName(ref name) => {
|
||||
self.state.username = Some(name.clone());
|
||||
self.action_tx.send(Action::NextScreen)?;
|
||||
}
|
||||
Action::TasksComplete => self.action_tx.send(Action::NextScreen)?,
|
||||
_ => {}
|
||||
}
|
||||
|
|
@ -403,10 +379,10 @@ impl App {
|
|||
|
||||
fn render(&mut self, tui: &mut Tui) -> Result<()> {
|
||||
tui.draw(|frame| {
|
||||
if let [header, _body, footer, center, left, right, username, popup] =
|
||||
if let [header, _body, footer, center, left, right, popup] =
|
||||
get_chunks(frame.area())[..]
|
||||
{
|
||||
let component_areas = vec![header, center, left, right, username, footer, popup];
|
||||
let component_areas = vec![header, center, left, right, footer, popup];
|
||||
for (component, area) in zip(self.components.iter_mut(), component_areas) {
|
||||
if let Err(err) = component.draw(frame, area) {
|
||||
let _ = self
|
||||
|
|
@ -428,12 +404,10 @@ fn build_footer_string(cur_mode: Mode) -> String {
|
|||
"(Enter) to select / / (i) to install driver / (r) to rescan / (q) to quit",
|
||||
),
|
||||
Mode::SelectTableType => String::from("(Enter) to select / (b) to go back / (q) to quit"),
|
||||
Mode::SelectWinSource | Mode::SelectWinImage => {
|
||||
String::from("(Enter) to select / (q) to quit")
|
||||
Mode::SelectWinImage => String::from("(Enter) to select / (q) to quit"),
|
||||
Mode::ScanWinImages => {
|
||||
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"),
|
||||
|
|
@ -476,33 +450,15 @@ fn build_left_items(app: &App) -> Action {
|
|||
title = String::from("Processing");
|
||||
// TODO: FIXME
|
||||
}
|
||||
Mode::ScanWinSources => {
|
||||
Mode::ScanWinImages => {
|
||||
select_type = SelectionType::Loop;
|
||||
title = String::from("Scanning");
|
||||
// TODO: FIXME
|
||||
}
|
||||
Mode::SelectWinSource => {
|
||||
select_type = SelectionType::One;
|
||||
title = String::from("Select Windows Source");
|
||||
if let Ok(wim_sources) = app.state.wim_sources.lock() {
|
||||
wim_sources
|
||||
.get_file_list()
|
||||
.iter()
|
||||
.for_each(|wim_file| items.push(wim_file.path.clone()));
|
||||
}
|
||||
}
|
||||
Mode::SelectWinImage | Mode::SetUserName => {
|
||||
Mode::SelectWinImage => {
|
||||
select_type = SelectionType::One;
|
||||
title = String::from("Select Windows Image");
|
||||
if let Ok(wim_sources) = app.state.wim_sources.lock()
|
||||
&& let Some(index) = app.state.wim_file_index
|
||||
{
|
||||
wim_sources
|
||||
.get_file(index)
|
||||
.images
|
||||
.iter()
|
||||
.for_each(|image| items.push(format!("{image}")));
|
||||
}
|
||||
// TODO: FIXME, I think this whole section could be better...
|
||||
}
|
||||
Mode::SelectDisks => {
|
||||
select_type = SelectionType::One;
|
||||
|
|
@ -518,6 +474,11 @@ fn build_left_items(app: &App) -> Action {
|
|||
items.push(format!("{}", PartitionTableType::Guid));
|
||||
items.push(format!("{}", PartitionTableType::Legacy));
|
||||
}
|
||||
Mode::SetUserName => {
|
||||
select_type = SelectionType::Loop;
|
||||
title = String::from("Customize");
|
||||
// TODO: FIXME
|
||||
}
|
||||
Mode::Confirm => {
|
||||
select_type = SelectionType::Loop;
|
||||
title = String::from("Confirm Selections");
|
||||
|
|
@ -590,158 +551,7 @@ fn build_right_items(app: &App) -> Action {
|
|||
.iter()
|
||||
.for_each(|disk| items.push(get_disk_description_right(disk, &None)));
|
||||
}
|
||||
Mode::SelectWinSource => {
|
||||
// Disk Info
|
||||
let type_str = match app.state.table_type.clone().unwrap() {
|
||||
PartitionTableType::Guid => "GPT",
|
||||
PartitionTableType::Legacy => "MBR",
|
||||
};
|
||||
let mut label_dv_lines = vec![
|
||||
DVLine {
|
||||
line_parts: vec![
|
||||
String::from("Dest"),
|
||||
String::from(" (WARNING: ALL DATA WILL BE DELETED!)"),
|
||||
],
|
||||
line_colors: vec![Color::Cyan, Color::Red],
|
||||
},
|
||||
DVLine {
|
||||
line_parts: vec![format!(" (Will be formatted {type_str})")],
|
||||
line_colors: vec![Color::Yellow],
|
||||
},
|
||||
DVLine::blank(),
|
||||
];
|
||||
let disk_list = app.state.disk_list.lock().unwrap();
|
||||
if let Some(index) = app.state.disk_index_dest
|
||||
&& let Some(disk) = disk_list.get(index)
|
||||
{
|
||||
get_disk_description_right(disk, &None)
|
||||
.into_iter()
|
||||
.for_each(|dv_line| label_dv_lines.push(dv_line));
|
||||
}
|
||||
labels.push(label_dv_lines);
|
||||
|
||||
// WIM Info
|
||||
if let Ok(wim_sources) = app.state.wim_sources.lock() {
|
||||
wim_sources.get_file_list().iter().for_each(|source| {
|
||||
let mut wim_dv_lines = vec![
|
||||
DVLine {
|
||||
line_parts: vec![String::from("WIM Info")],
|
||||
line_colors: vec![Color::Cyan],
|
||||
},
|
||||
DVLine {
|
||||
line_parts: vec![source.path.clone()],
|
||||
line_colors: vec![Color::Reset],
|
||||
},
|
||||
DVLine::blank(),
|
||||
DVLine {
|
||||
line_parts: vec![String::from("Images")],
|
||||
line_colors: vec![Color::Blue],
|
||||
},
|
||||
DVLine::blank(),
|
||||
];
|
||||
source.images.iter().for_each(|image| {
|
||||
wim_dv_lines.push(DVLine {
|
||||
line_parts: vec![format!("{image}")],
|
||||
line_colors: vec![Color::Reset],
|
||||
})
|
||||
});
|
||||
items.push(wim_dv_lines);
|
||||
});
|
||||
}
|
||||
}
|
||||
Mode::SelectWinImage | Mode::SetUserName | Mode::Confirm => {
|
||||
info!("Building right items for: {:?}", &app.cur_mode);
|
||||
let wim_file;
|
||||
if let Ok(wim_sources) = app.state.wim_sources.lock()
|
||||
&& let Some(index) = app.state.wim_file_index
|
||||
{
|
||||
wim_file = wim_sources.get_file(index);
|
||||
} else {
|
||||
panic!("Failed to get source WIM file");
|
||||
}
|
||||
// Disk Info
|
||||
let type_str = match app.state.table_type.clone().unwrap() {
|
||||
PartitionTableType::Guid => "GPT",
|
||||
PartitionTableType::Legacy => "MBR",
|
||||
};
|
||||
let mut label_dv_lines = vec![
|
||||
DVLine {
|
||||
line_parts: vec![
|
||||
String::from("Dest"),
|
||||
String::from(" (WARNING: ALL DATA WILL BE DELETED!)"),
|
||||
],
|
||||
line_colors: vec![Color::Cyan, Color::Red],
|
||||
},
|
||||
DVLine {
|
||||
line_parts: vec![format!(" (Will be formatted {type_str})")],
|
||||
line_colors: vec![Color::Yellow],
|
||||
},
|
||||
DVLine::blank(),
|
||||
];
|
||||
let disk_list = app.state.disk_list.lock().unwrap();
|
||||
if let Some(index) = app.state.disk_index_dest
|
||||
&& let Some(disk) = disk_list.get(index)
|
||||
{
|
||||
get_disk_description_right(disk, &None)
|
||||
.into_iter()
|
||||
.for_each(|dv_line| label_dv_lines.push(dv_line));
|
||||
}
|
||||
label_dv_lines.append(&mut vec![
|
||||
DVLine::blank(),
|
||||
DVLine {
|
||||
line_parts: vec![String::from("WIM Info")],
|
||||
line_colors: vec![Color::Cyan],
|
||||
},
|
||||
DVLine {
|
||||
line_parts: vec![wim_file.path.clone()],
|
||||
line_colors: vec![Color::Reset],
|
||||
},
|
||||
DVLine::blank(),
|
||||
DVLine {
|
||||
line_parts: vec![String::from("Image")],
|
||||
line_colors: vec![Color::Blue],
|
||||
},
|
||||
]);
|
||||
|
||||
// WIM Info
|
||||
match app.cur_mode {
|
||||
Mode::SelectWinImage => {
|
||||
wim_file.images.iter().for_each(|image| {
|
||||
items.push(vec![DVLine {
|
||||
line_parts: vec![format!("{image}")],
|
||||
line_colors: vec![Color::Reset],
|
||||
}])
|
||||
});
|
||||
}
|
||||
Mode::Confirm => {
|
||||
if let Some(index) = app.state.wim_image_index
|
||||
&& let Some(image) = wim_file.images.get(index)
|
||||
{
|
||||
label_dv_lines.append(&mut vec![
|
||||
DVLine {
|
||||
line_parts: vec![format!("{image}")],
|
||||
line_colors: vec![Color::Reset],
|
||||
},
|
||||
DVLine::blank(),
|
||||
]);
|
||||
}
|
||||
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],
|
||||
}]);
|
||||
}
|
||||
items.push(vec![DVLine::blank()]);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Done
|
||||
labels.push(label_dv_lines);
|
||||
}
|
||||
Mode::SelectTableType => {
|
||||
Mode::SelectTableType | Mode::SelectWinImage | Mode::SetUserName | Mode::Confirm => {
|
||||
// Labels
|
||||
let dest_dv_line = DVLine {
|
||||
line_parts: vec![
|
||||
|
|
@ -829,9 +639,6 @@ fn get_chunks(r: Rect) -> Vec<Rect> {
|
|||
// Center
|
||||
chunks.push(center);
|
||||
|
||||
// Set username
|
||||
chunks.push(centered_rect(60, 20, r));
|
||||
|
||||
// Popup
|
||||
chunks.push(centered_rect(60, 25, r));
|
||||
|
||||
|
|
|
|||
|
|
@ -13,5 +13,4 @@
|
|||
// You should have received a copy of the GNU General Public License
|
||||
// along with Deja-Vu. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
pub mod set_username;
|
||||
pub mod wim_scan;
|
||||
|
|
|
|||
|
|
@ -1,135 +0,0 @@
|
|||
// 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 core::{action::Action, components::Component, config::Config, state::Mode, tui::Event};
|
||||
use crossterm::event::KeyCode;
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
widgets::{Block, Borders, Clear, Paragraph},
|
||||
};
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
use tui_input::{Input, InputRequest};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct InputUsername {
|
||||
command_tx: Option<UnboundedSender<Action>>,
|
||||
config: Config,
|
||||
input: Input,
|
||||
mode: Mode,
|
||||
}
|
||||
|
||||
impl InputUsername {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
input: Input::new(String::from("")),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for InputUsername {
|
||||
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(())
|
||||
}
|
||||
|
||||
fn handle_events(&mut self, event: Option<Event>) -> Result<Option<Action>> {
|
||||
if self.mode != Mode::SetUserName {
|
||||
return Ok(None);
|
||||
}
|
||||
let action = match event {
|
||||
Some(Event::Key(key_event)) => match key_event.code {
|
||||
KeyCode::Backspace => {
|
||||
self.input.handle(InputRequest::DeletePrevChar);
|
||||
None
|
||||
}
|
||||
KeyCode::Char(c) => {
|
||||
let ok_chars: Vec<char> = vec![' ', '-', '_'];
|
||||
if c.is_ascii_alphanumeric() || ok_chars.contains(&c) {
|
||||
self.input.handle(InputRequest::InsertChar(c));
|
||||
}
|
||||
None
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
let username = self.input.value();
|
||||
Some(Action::SetUserName(String::from(username)))
|
||||
}
|
||||
KeyCode::Esc => Some(Action::SetMode(Mode::Home)),
|
||||
_ => None,
|
||||
},
|
||||
Some(Event::Mouse(_)) => None,
|
||||
_ => None,
|
||||
};
|
||||
Ok(action)
|
||||
}
|
||||
|
||||
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
||||
match action {
|
||||
Action::SetMode(mode) => self.mode = mode.clone(),
|
||||
_ => {}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
|
||||
if self.mode != Mode::SetUserName {
|
||||
// Bail early
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Set areas
|
||||
let [_, center_area, _] = Layout::horizontal([
|
||||
Constraint::Length(1),
|
||||
Constraint::Min(1),
|
||||
Constraint::Length(1),
|
||||
])
|
||||
.areas(area);
|
||||
let [_, input_area, _] = Layout::vertical([
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(1),
|
||||
])
|
||||
.areas(center_area);
|
||||
|
||||
frame.render_widget(Clear, area);
|
||||
let outer_block = Block::bordered().cyan().bold();
|
||||
frame.render_widget(outer_block, area);
|
||||
|
||||
// Input Box
|
||||
let width = input_area.width.max(3) - 3; // keep 2 for borders and 1 for cursor
|
||||
let scroll = self.input.visual_scroll(width as usize);
|
||||
let input = Paragraph::new(self.input.value())
|
||||
.scroll((0, scroll as u16))
|
||||
.white()
|
||||
.block(Block::new().borders(Borders::ALL).title("Enter Username"));
|
||||
frame.render_widget(input, input_area);
|
||||
|
||||
// Ratatui hides the cursor unless it's explicitly set. Position the cursor past the
|
||||
// end of the input text and one line down from the border to the input line
|
||||
let x = self.input.visual_cursor().max(scroll) - scroll + 1;
|
||||
frame.set_cursor_position((input_area.x + x as u16, input_area.y + 1));
|
||||
|
||||
// Done
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -77,7 +77,7 @@ impl Component for WimScan {
|
|||
}
|
||||
|
||||
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
|
||||
if self.mode != Mode::ScanWinSources {
|
||||
if self.mode != Mode::ScanWinImages {
|
||||
return Ok(());
|
||||
}
|
||||
frame.render_widget(Clear, area);
|
||||
|
|
@ -117,26 +117,36 @@ impl Component for WimScan {
|
|||
.iter()
|
||||
.map(|wimfile| ListItem::new(format!("{}\n\n", wimfile.path))),
|
||||
);
|
||||
let left_list = List::new(left_list).block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.padding(Padding::new(1, 1, 1, 1)),
|
||||
);
|
||||
let left_list = List::new(left_list)
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.padding(Padding::new(1, 1, 1, 1)),
|
||||
)
|
||||
.highlight_spacing(HighlightSpacing::Always)
|
||||
.highlight_style(Style::new().green().bold())
|
||||
.highlight_symbol(" --> ")
|
||||
.repeat_highlight_symbol(false);
|
||||
frame.render_widget(left_list, left_body);
|
||||
|
||||
// Network
|
||||
let mut right_list = Vec::new();
|
||||
right_list.extend(wim_sources.network.iter().map(|wimfile| {
|
||||
ListItem::new(format!(
|
||||
"{}\n\n",
|
||||
wimfile.path.split("\\").last().unwrap_or(&wimfile.path)
|
||||
))
|
||||
}));
|
||||
let right_list = List::new(right_list).block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.padding(Padding::new(1, 1, 1, 1)),
|
||||
right_list.extend(
|
||||
wim_sources
|
||||
.network
|
||||
.iter()
|
||||
.map(|wimfile| ListItem::new(format!("{}\n\n", wimfile.path))),
|
||||
);
|
||||
let right_list = List::new(right_list)
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.padding(Padding::new(1, 1, 1, 1)),
|
||||
)
|
||||
.highlight_spacing(HighlightSpacing::Always)
|
||||
.highlight_style(Style::new().green().bold())
|
||||
.highlight_symbol(" --> ")
|
||||
.repeat_highlight_symbol(false);
|
||||
frame.render_widget(right_list, right_body);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ use crate::app::App;
|
|||
|
||||
mod app;
|
||||
mod components;
|
||||
mod net;
|
||||
mod state;
|
||||
mod wim;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,68 +0,0 @@
|
|||
// 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/>.
|
||||
//
|
||||
|
||||
//#![windows_subsystem = "windows"]
|
||||
use std::ffi::CString;
|
||||
|
||||
use windows_sys::Win32::Foundation::NO_ERROR;
|
||||
use windows_sys::Win32::NetworkManagement::WNet;
|
||||
|
||||
fn to_cstr(s: &str) -> CString {
|
||||
CString::new(s).unwrap()
|
||||
}
|
||||
|
||||
pub fn connect_network_share(
|
||||
server: &str,
|
||||
share: &str,
|
||||
username: &str,
|
||||
password: &str,
|
||||
) -> Result<(), u32> {
|
||||
let remote_name = to_cstr(&format!("\\\\{server}\\{share}"));
|
||||
|
||||
// init resources
|
||||
let mut resources = WNet::NETRESOURCEA {
|
||||
dwDisplayType: WNet::RESOURCEDISPLAYTYPE_SHAREADMIN,
|
||||
dwScope: WNet::RESOURCE_GLOBALNET,
|
||||
dwType: WNet::RESOURCETYPE_DISK,
|
||||
dwUsage: WNet::RESOURCEUSAGE_ALL,
|
||||
lpComment: std::ptr::null_mut(),
|
||||
lpLocalName: std::ptr::null_mut(), // PUT a volume here if you want to mount as a windows volume
|
||||
lpProvider: std::ptr::null_mut(),
|
||||
lpRemoteName: remote_name.as_c_str().as_ptr() as *mut u8,
|
||||
};
|
||||
|
||||
let username = to_cstr(username);
|
||||
let password = to_cstr(password);
|
||||
|
||||
// mount
|
||||
let result = unsafe {
|
||||
let username_ptr = username.as_ptr();
|
||||
let password_ptr = password.as_ptr();
|
||||
WNet::WNetAddConnection2A(
|
||||
&mut resources as *mut WNet::NETRESOURCEA,
|
||||
password_ptr as *const u8,
|
||||
username_ptr as *const u8,
|
||||
//WNet::CONNECT_INTERACTIVE, // Interactive will show a system dialog in case credentials are wrong to retry with the password. Put 0 if you don't want it
|
||||
0,
|
||||
)
|
||||
};
|
||||
|
||||
if result == NO_ERROR {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(result)
|
||||
}
|
||||
}
|
||||
|
|
@ -14,52 +14,33 @@
|
|||
// along with Deja-Vu. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
use std::{
|
||||
fs::read_dir,
|
||||
sync::{Arc, Mutex},
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use core::system::{
|
||||
disk::{Disk, PartitionTableType},
|
||||
drivers,
|
||||
};
|
||||
|
||||
use core::{
|
||||
config::Config,
|
||||
system::{
|
||||
disk::{Disk, PartitionTableType},
|
||||
drivers,
|
||||
},
|
||||
};
|
||||
|
||||
use tracing::info;
|
||||
|
||||
use crate::{
|
||||
net::connect_network_share,
|
||||
wim::{WimFile, WimSources, parse_wim_file},
|
||||
};
|
||||
|
||||
pub enum ScanType {
|
||||
GeneralWimFiles, // Includes Windows installer WIMs
|
||||
WindowsInstallers,
|
||||
}
|
||||
use crate::wim::WimSources;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct State {
|
||||
pub config: Config,
|
||||
pub disk_index_dest: Option<usize>,
|
||||
pub disk_list: Arc<Mutex<Vec<Disk>>>,
|
||||
pub driver: Option<drivers::Driver>,
|
||||
pub driver_list: Vec<drivers::Driver>,
|
||||
pub table_type: Option<PartitionTableType>,
|
||||
pub username: Option<String>,
|
||||
pub wim_file_index: Option<usize>,
|
||||
pub wim_image_index: Option<usize>,
|
||||
pub wim_sources: Arc<Mutex<WimSources>>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn new(config: Config, disk_list: Arc<Mutex<Vec<Disk>>>) -> Self {
|
||||
let wim_sources = Arc::new(Mutex::new(WimSources::new()));
|
||||
pub fn new(disk_list: Arc<Mutex<Vec<Disk>>>) -> Self {
|
||||
let wim_sources = WimSources::new();
|
||||
State {
|
||||
config,
|
||||
disk_list,
|
||||
wim_sources,
|
||||
wim_sources: Arc::new(Mutex::new(wim_sources)),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
|
@ -73,12 +54,6 @@ 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();
|
||||
|
|
@ -88,122 +63,4 @@ impl State {
|
|||
pub fn scan_drivers(&mut self) {
|
||||
self.driver_list = drivers::scan();
|
||||
}
|
||||
|
||||
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_type);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn scan_wim_network(&mut self) {
|
||||
let config = self.config.clone();
|
||||
let wim_sources_arc = self.wim_sources.clone();
|
||||
tokio::task::spawn(async move {
|
||||
scan_network_share(config, wim_sources_arc);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
match scan_type {
|
||||
ScanType::GeneralWimFiles => {
|
||||
to_check.append(&mut get_subfolders(&format!("{}:\\", &p.letter)));
|
||||
}
|
||||
ScanType::WindowsInstallers => {
|
||||
to_check.push(format!("{}:\\Images", &p.letter));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
// 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, installer)
|
||||
{
|
||||
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>>) {
|
||||
let result = connect_network_share(
|
||||
&config.network_server,
|
||||
&config.network_share,
|
||||
&config.network_user,
|
||||
&config.network_pass,
|
||||
);
|
||||
let mut wim_files: Vec<WimFile> = Vec::new();
|
||||
|
||||
// Connect to share
|
||||
if result.is_err() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Scan share
|
||||
let share_dir = format!("\\\\{}\\{}", &config.network_server, &config.network_share);
|
||||
if let Ok(read_dir) = read_dir(share_dir) {
|
||||
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, true)
|
||||
// Assuming all network sources are installers
|
||||
{
|
||||
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_network(file));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use core::system::disk::Disk;
|
||||
// This file is part of Deja-Vu.
|
||||
//
|
||||
// Deja-Vu is free software: you can redistribute it and/or modify it
|
||||
|
|
@ -14,28 +15,19 @@
|
|||
// along with Deja-Vu. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::HashMap,
|
||||
env, fmt,
|
||||
fs::File,
|
||||
fmt,
|
||||
fs::{File, read_dir},
|
||||
io::BufReader,
|
||||
path::{Path, PathBuf},
|
||||
path::Path,
|
||||
process::Command,
|
||||
sync::LazyLock,
|
||||
sync::{Arc, LazyLock, Mutex},
|
||||
};
|
||||
|
||||
use tempfile::NamedTempFile;
|
||||
use tracing::info;
|
||||
use xml::reader::{EventReader, XmlEvent};
|
||||
|
||||
static WIMINFO_EXE: LazyLock<String> = LazyLock::new(|| {
|
||||
let program_files =
|
||||
PathBuf::from(env::var("PROGRAMFILES").expect("Failed to resolve %PROGRAMFILES%"));
|
||||
program_files
|
||||
.join("wimlib/wiminfo.cmd")
|
||||
.to_string_lossy()
|
||||
.into_owned()
|
||||
});
|
||||
|
||||
static WIN_BUILDS: LazyLock<HashMap<&str, &str>> = LazyLock::new(|| {
|
||||
HashMap::from([
|
||||
// Windows 10
|
||||
|
|
@ -62,49 +54,10 @@ static WIN_BUILDS: LazyLock<HashMap<&str, &str>> = LazyLock::new(|| {
|
|||
])
|
||||
});
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Debug)]
|
||||
pub struct WimFile {
|
||||
pub path: String,
|
||||
pub images: Vec<WimImage>,
|
||||
pub is_installer: bool,
|
||||
}
|
||||
|
||||
impl WimFile {
|
||||
pub fn summary(&self) -> String {
|
||||
let mut s = format!("{self}");
|
||||
self.images.iter().for_each(|image| {
|
||||
let image = format!("\n\t\t{image}");
|
||||
s.push_str(&image);
|
||||
});
|
||||
|
||||
s
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for WimFile {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.path.split("\\").last().unwrap_or(&self.path))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for WimFile {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.path == other.path
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for WimFile {}
|
||||
|
||||
impl Ord for WimFile {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.path.cmp(&other.path)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for WimFile {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
|
|
@ -153,39 +106,11 @@ impl WimSources {
|
|||
Default::default()
|
||||
}
|
||||
|
||||
pub fn add_local(&mut self, wim_file: WimFile) {
|
||||
self.local.push(wim_file);
|
||||
}
|
||||
|
||||
pub fn add_network(&mut self, wim_file: WimFile) {
|
||||
self.network.push(wim_file);
|
||||
}
|
||||
|
||||
pub fn get_file(&self, index: usize) -> WimFile {
|
||||
let num_local = self.local.len();
|
||||
let index = if index < num_local {
|
||||
index
|
||||
} else {
|
||||
index - num_local
|
||||
};
|
||||
self.local.get(index).unwrap().clone()
|
||||
}
|
||||
|
||||
pub fn get_file_list(&self) -> Vec<WimFile> {
|
||||
let mut list = self.local.clone();
|
||||
list.append(&mut self.network.clone());
|
||||
list
|
||||
}
|
||||
|
||||
pub fn reset_all(&mut self) {
|
||||
self.local.clear();
|
||||
self.network.clear();
|
||||
}
|
||||
|
||||
pub fn reset_local(&mut self) {
|
||||
self.local.clear();
|
||||
}
|
||||
|
||||
pub fn reset_network(&mut self) {
|
||||
self.network.clear();
|
||||
}
|
||||
|
|
@ -193,7 +118,7 @@ impl WimSources {
|
|||
|
||||
fn get_wim_xml(wim_file: &str) -> std::io::Result<File> {
|
||||
let tmp_file = NamedTempFile::new()?;
|
||||
let _ = Command::new(&*WIMINFO_EXE)
|
||||
let _ = Command::new("wiminfo")
|
||||
.args([
|
||||
wim_file,
|
||||
"--extract-xml",
|
||||
|
|
@ -206,7 +131,7 @@ fn get_wim_xml(wim_file: &str) -> std::io::Result<File> {
|
|||
Ok(file)
|
||||
}
|
||||
|
||||
pub fn parse_wim_file(wim_file: &str, installer: bool) -> std::io::Result<WimFile> {
|
||||
pub fn parse_wim_file(wim_file: &str) -> std::io::Result<WimFile> {
|
||||
let mut wim_images: Vec<WimImage> = Vec::new();
|
||||
if !Path::new(wim_file).exists() {
|
||||
return Err(std::io::Error::new(
|
||||
|
|
@ -271,8 +196,42 @@ pub fn parse_wim_file(wim_file: &str, installer: bool) -> std::io::Result<WimFil
|
|||
let wim_file = WimFile {
|
||||
path: wim_file.to_string(),
|
||||
images: wim_images,
|
||||
is_installer: installer,
|
||||
};
|
||||
|
||||
Ok(wim_file)
|
||||
}
|
||||
|
||||
pub fn scan_local_drives(
|
||||
disk_list_arc: Arc<Mutex<Vec<Disk>>>,
|
||||
wim_sources_arc: Arc<Mutex<WimSources>>,
|
||||
) {
|
||||
let mut to_check = vec![String::from(".")];
|
||||
|
||||
// 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));
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
// Scan drives
|
||||
to_check.iter().for_each(|scan_path| {
|
||||
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()
|
||||
{
|
||||
wim_sources.local.push(new_source);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue