Compare commits
3 commits
cf87ac32af
...
3ce36c5a0f
| Author | SHA1 | Date | |
|---|---|---|---|
| 3ce36c5a0f | |||
| e0932c7b48 | |||
| f51a4e85c4 |
11 changed files with 639 additions and 68 deletions
|
|
@ -1,2 +1,17 @@
|
|||
// 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/>.
|
||||
//
|
||||
pub mod logview;
|
||||
pub mod progress;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,18 @@
|
|||
// 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::system::disk::PartitionTableType;
|
||||
use core::tasks::Tasks;
|
||||
|
|
|
|||
|
|
@ -188,5 +188,29 @@
|
|||
"<Ctrl-c>": "Quit",
|
||||
"<Ctrl-z>": "Suspend"
|
||||
},
|
||||
"ScanWinImages": {
|
||||
"<Enter>": "Process",
|
||||
"<n>": "FindWimNetwork",
|
||||
"<q>": "Quit",
|
||||
"<Ctrl-d>": "Quit",
|
||||
"<Ctrl-c>": "Quit",
|
||||
"<Ctrl-z>": "Suspend"
|
||||
},
|
||||
"SelectWinImage": {
|
||||
"<Enter>": "Process",
|
||||
"<Up>": "KeyUp",
|
||||
"<Down>": "KeyDown",
|
||||
"<q>": "Quit",
|
||||
"<Ctrl-d>": "Quit",
|
||||
"<Ctrl-c>": "Quit",
|
||||
"<Ctrl-z>": "Suspend"
|
||||
},
|
||||
"SetUserName": {
|
||||
"<Enter>": "Process",
|
||||
"<Esc>": "PrevScreen",
|
||||
"<Ctrl-d>": "Quit",
|
||||
"<Ctrl-c>": "Quit",
|
||||
"<Ctrl-z>": "Suspend"
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,6 +55,8 @@ pub enum Action {
|
|||
OpenTerminal,
|
||||
Restart,
|
||||
Shutdown,
|
||||
// App (Win-Installer)
|
||||
FindWimNetwork,
|
||||
// Screens
|
||||
DismissPopup,
|
||||
DisplayPopup(PopupType, String),
|
||||
|
|
|
|||
|
|
@ -467,9 +467,8 @@ impl App {
|
|||
self.set_mode(new_mode)?;
|
||||
self.action_tx
|
||||
.send(Action::UpdateFooter(build_footer_string(self.cur_mode)))?;
|
||||
self.action_tx.send(build_left_items(self, self.cur_mode))?;
|
||||
self.action_tx
|
||||
.send(build_right_items(self, self.cur_mode))?;
|
||||
self.action_tx.send(build_left_items(self))?;
|
||||
self.action_tx.send(build_right_items(self))?;
|
||||
match new_mode {
|
||||
Mode::SelectTableType | Mode::Confirm => {
|
||||
// Select source/dest disks
|
||||
|
|
@ -647,12 +646,12 @@ fn build_footer_string(cur_mode: Mode) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
fn build_left_items(app: &App, cur_mode: Mode) -> Action {
|
||||
fn build_left_items(app: &App) -> Action {
|
||||
let select_type: SelectionType;
|
||||
let title: String;
|
||||
let mut items = Vec::new();
|
||||
let mut labels: Vec<String> = Vec::new();
|
||||
match cur_mode {
|
||||
match app.cur_mode {
|
||||
Mode::Home => {
|
||||
select_type = SelectionType::Loop;
|
||||
title = String::from("Home");
|
||||
|
|
@ -724,11 +723,11 @@ fn build_left_items(app: &App, cur_mode: Mode) -> Action {
|
|||
Action::UpdateLeft(title, labels, items, select_type)
|
||||
}
|
||||
|
||||
fn build_right_items(app: &App, cur_mode: Mode) -> Action {
|
||||
fn build_right_items(app: &App) -> Action {
|
||||
let mut items = Vec::new();
|
||||
let mut labels: Vec<Vec<DVLine>> = Vec::new();
|
||||
let mut start_index = 0;
|
||||
match cur_mode {
|
||||
match app.cur_mode {
|
||||
Mode::InstallDrivers => {
|
||||
items.push(vec![DVLine {
|
||||
line_parts: vec![String::from("CPU")],
|
||||
|
|
|
|||
|
|
@ -15,15 +15,23 @@
|
|||
//
|
||||
use core::{
|
||||
action::Action,
|
||||
components::{Component, footer::Footer, left::Left, popup, right::Right, title::Title},
|
||||
components::{
|
||||
Component,
|
||||
footer::Footer,
|
||||
left::{Left, SelectionType},
|
||||
popup,
|
||||
right::Right,
|
||||
title::Title,
|
||||
},
|
||||
config::Config,
|
||||
line::{DVLine, get_disk_description_right},
|
||||
state::Mode,
|
||||
system::{cpu::get_cpu_name, disk::PartitionTableType, drivers},
|
||||
tasks::{TaskType, Tasks},
|
||||
tui::{Event, Tui},
|
||||
};
|
||||
use std::{
|
||||
iter::zip,
|
||||
path::PathBuf,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
|
|
@ -32,13 +40,14 @@ use ratatui::{
|
|||
crossterm::event::KeyEvent,
|
||||
layout::{Constraint, Direction, Layout},
|
||||
prelude::Rect,
|
||||
style::Color,
|
||||
};
|
||||
use tokio::sync::mpsc;
|
||||
use tracing::{debug, info};
|
||||
|
||||
use crate::state::State;
|
||||
use crate::{components::wim_scan::WimScan, state::State, wim::parse_wim_file};
|
||||
|
||||
pub struct App<'a> {
|
||||
pub struct App {
|
||||
// TUI
|
||||
action_rx: mpsc::UnboundedReceiver<Action>,
|
||||
action_tx: mpsc::UnboundedSender<Action>,
|
||||
|
|
@ -50,16 +59,18 @@ pub struct App<'a> {
|
|||
should_suspend: bool,
|
||||
tick_rate: f64,
|
||||
// App
|
||||
state: State<'a>,
|
||||
mode: Mode,
|
||||
state: State,
|
||||
cur_mode: Mode,
|
||||
tasks: Tasks,
|
||||
}
|
||||
|
||||
impl App<'_> {
|
||||
impl App {
|
||||
pub fn new(tick_rate: f64, frame_rate: f64) -> Result<Self> {
|
||||
let (action_tx, action_rx) = mpsc::unbounded_channel();
|
||||
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(disk_list_arc);
|
||||
let wim_sources = Arc::clone(&state.wim_sources);
|
||||
Ok(Self {
|
||||
// TUI
|
||||
action_rx,
|
||||
|
|
@ -68,6 +79,7 @@ impl App<'_> {
|
|||
Box::new(Title::new("Windows Install Tool")),
|
||||
Box::new(Left::new()),
|
||||
Box::new(Right::new()),
|
||||
Box::new(WimScan::new(wim_sources)),
|
||||
Box::new(Footer::new()),
|
||||
Box::new(popup::Popup::new()),
|
||||
],
|
||||
|
|
@ -78,14 +90,14 @@ impl App<'_> {
|
|||
should_suspend: false,
|
||||
tick_rate,
|
||||
// App
|
||||
mode: Mode::default(),
|
||||
state: State::new(disk_list_arc),
|
||||
cur_mode: Mode::default(),
|
||||
state,
|
||||
tasks,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn next_mode(&mut self) -> Mode {
|
||||
match self.mode {
|
||||
match self.cur_mode {
|
||||
Mode::Home | Mode::InstallDrivers => Mode::ScanDisks,
|
||||
Mode::ScanDisks => Mode::SelectDisks,
|
||||
Mode::SelectDisks => Mode::SelectTableType,
|
||||
|
|
@ -112,6 +124,41 @@ impl App<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn set_mode(&mut self, new_mode: Mode) -> Result<()> {
|
||||
info!("Setting mode to {new_mode:?}");
|
||||
self.cur_mode = new_mode;
|
||||
match new_mode {
|
||||
Mode::InstallDrivers => self.state.scan_drivers(),
|
||||
// Mode::Process => {
|
||||
// self.action_tx
|
||||
// .send(Action::DisplayPopup(popup::Type::Info, String::from("...")))?;
|
||||
// }
|
||||
Mode::ScanDisks => {
|
||||
self.state.reset_all();
|
||||
if self.tasks.idle() {
|
||||
self.tasks.add(TaskType::ScanDisks);
|
||||
}
|
||||
self.action_tx.send(Action::DisplayPopup(
|
||||
popup::Type::Info,
|
||||
String::from("Scanning Disks..."),
|
||||
))?;
|
||||
}
|
||||
Mode::ScanWinImages => {
|
||||
// TODO: DELETEME
|
||||
let mut wim_sources = self.state.wim_sources.lock().unwrap();
|
||||
wim_sources
|
||||
.local
|
||||
.push(parse_wim_file("/home/twoshirt/Projects/deja-vu/23H2.wim")?);
|
||||
}
|
||||
Mode::Done => {
|
||||
self.action_tx
|
||||
.send(Action::DisplayPopup(popup::Type::Success, popup::fortune()))?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) -> Result<()> {
|
||||
let mut tui = Tui::new()?
|
||||
// .mouse(true) // uncomment this line to enable mouse support
|
||||
|
|
@ -172,7 +219,7 @@ impl App<'_> {
|
|||
|
||||
fn handle_key_event(&mut self, key: KeyEvent) -> Result<()> {
|
||||
let action_tx = self.action_tx.clone();
|
||||
let Some(keymap) = self.config.keybindings.get(&self.mode) else {
|
||||
let Some(keymap) = self.config.keybindings.get(&self.cur_mode) else {
|
||||
return Ok(());
|
||||
};
|
||||
if let Some(action) = keymap.get(&vec![key]) {
|
||||
|
|
@ -236,27 +283,90 @@ impl App<'_> {
|
|||
.send(Action::DisplayPopup(popup::Type::Error, msg.clone()))?;
|
||||
self.action_tx.send(Action::SetMode(Mode::Failed))?;
|
||||
}
|
||||
// Action::Process => {
|
||||
// // Run selected tool
|
||||
// if let Some(tool) = self.list.get_selected() {
|
||||
// info!("Run tool: {:?}", &tool);
|
||||
// self.tasks.add(build_tool_command(self, &tool));
|
||||
// }
|
||||
// }
|
||||
Action::Resize(w, h) => self.handle_resize(tui, w, h)?,
|
||||
Action::Render => self.render(tui)?,
|
||||
Action::SetMode(mode) => {
|
||||
self.mode = mode;
|
||||
if self.mode == Mode::ScanDisks {
|
||||
self.state.reset();
|
||||
Action::InstallDriver => {
|
||||
self.action_tx.send(Action::SetMode(Mode::InstallDrivers))?;
|
||||
}
|
||||
Action::FindWimNetwork => {
|
||||
self.state.reset_network();
|
||||
let mut wim_sources = self.state.wim_sources.lock().unwrap();
|
||||
wim_sources
|
||||
.network
|
||||
.push(parse_wim_file("/home/twoshirt/Projects/deja-vu/23H2.wim")?);
|
||||
wim_sources
|
||||
.network
|
||||
.push(parse_wim_file("/home/twoshirt/Projects/deja-vu/24H2.wim")?);
|
||||
// TODO: Actually scan network!
|
||||
}
|
||||
Action::NextScreen => {
|
||||
let next_mode = self.next_mode();
|
||||
self.action_tx.send(Action::DismissPopup)?;
|
||||
self.action_tx.send(Action::SetMode(next_mode))?;
|
||||
}
|
||||
Action::PrevScreen => match self.cur_mode {
|
||||
Mode::SelectTableType => {
|
||||
self.action_tx.send(Action::SetMode(Mode::SelectDisks))?;
|
||||
}
|
||||
self.action_tx.send(Action::UpdateFooter(String::from(
|
||||
"(Enter) to select / (t) for terminal / (p) to power off / (r) to restart",
|
||||
)))?;
|
||||
//self.action_tx.send(build_left_items(self))?;
|
||||
//self.action_tx.send(build_right_items(self))?;
|
||||
Mode::SetUserName => {
|
||||
self.action_tx.send(Action::SetMode(Mode::SelectWinImage))?;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Action::Process => match self.cur_mode {
|
||||
Mode::Confirm | Mode::ScanWinImages => {
|
||||
self.action_tx.send(Action::NextScreen)?;
|
||||
}
|
||||
Mode::Done => {
|
||||
self.action_tx.send(Action::Quit)?;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Action::ScanDisks => self.action_tx.send(Action::SetMode(Mode::ScanDisks))?,
|
||||
Action::Select(one, _two) => match self.cur_mode {
|
||||
Mode::InstallDrivers => {
|
||||
if let Some(index) = one
|
||||
&& let Some(driver) = self.state.driver_list.get(index).cloned()
|
||||
{
|
||||
drivers::load(&driver.inf_paths);
|
||||
self.state.driver = Some(driver);
|
||||
}
|
||||
}
|
||||
Mode::SelectDisks => {
|
||||
self.state.disk_index_dest = one;
|
||||
}
|
||||
Mode::SelectTableType => {
|
||||
self.state.table_type = {
|
||||
if let Some(index) = one {
|
||||
match index {
|
||||
0 => Some(PartitionTableType::Guid),
|
||||
1 => Some(PartitionTableType::Legacy),
|
||||
index => {
|
||||
panic!("Failed to select PartitionTableType: {index}")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
Mode::SelectWinImage => {
|
||||
// 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;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Action::SetMode(mode) => {
|
||||
self.set_mode(mode)?;
|
||||
self.action_tx
|
||||
.send(Action::UpdateFooter(build_footer_string(self.cur_mode)))?;
|
||||
self.action_tx.send(build_left_items(self))?;
|
||||
self.action_tx.send(build_right_items(self))?;
|
||||
self.action_tx.send(Action::Select(None, None))?;
|
||||
}
|
||||
Action::TasksComplete => self.action_tx.send(Action::NextScreen)?,
|
||||
_ => {}
|
||||
}
|
||||
for component in &mut self.components {
|
||||
|
|
@ -276,8 +386,10 @@ impl App<'_> {
|
|||
|
||||
fn render(&mut self, tui: &mut Tui) -> Result<()> {
|
||||
tui.draw(|frame| {
|
||||
if let [header, _body, footer, left, right, popup] = get_chunks(frame.area())[..] {
|
||||
let component_areas = vec![header, left, right, footer, popup];
|
||||
if let [header, _body, footer, center, left, right, popup] =
|
||||
get_chunks(frame.area())[..]
|
||||
{
|
||||
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
|
||||
|
|
@ -291,6 +403,198 @@ impl App<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
fn build_footer_string(cur_mode: Mode) -> String {
|
||||
match cur_mode {
|
||||
Mode::Home | Mode::ScanDisks => String::from("(q) to quit"),
|
||||
Mode::InstallDrivers => String::from("(Enter) to select / (q) to quit"),
|
||||
Mode::SelectDisks => String::from(
|
||||
"(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::SelectWinImage => String::from("(Enter) to select / (q) to quit"),
|
||||
Mode::ScanWinImages => {
|
||||
String::from("(Enter) to continue / (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"),
|
||||
// Invalid States
|
||||
Mode::BootDiags
|
||||
| Mode::BootScan
|
||||
| Mode::BootSetup
|
||||
| Mode::Clone
|
||||
| Mode::DiagMenu
|
||||
| Mode::InjectDrivers
|
||||
| Mode::LogView
|
||||
| Mode::PEMenu
|
||||
| Mode::PreClone
|
||||
| Mode::PostClone
|
||||
| Mode::SelectParts
|
||||
| Mode::SetBootMode => panic!("This shouldn't happen?"),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_left_items(app: &App) -> Action {
|
||||
let select_type: SelectionType;
|
||||
let title: String;
|
||||
let mut items = Vec::new();
|
||||
let mut labels: Vec<String> = Vec::new();
|
||||
match app.cur_mode {
|
||||
Mode::Home => {
|
||||
select_type = SelectionType::Loop;
|
||||
title = String::from("Home");
|
||||
}
|
||||
Mode::InstallDrivers => {
|
||||
select_type = SelectionType::One;
|
||||
title = String::from("Install Drivers");
|
||||
app.state
|
||||
.driver_list
|
||||
.iter()
|
||||
.for_each(|driver| items.push(driver.to_string()));
|
||||
}
|
||||
Mode::Process => {
|
||||
select_type = SelectionType::Loop;
|
||||
title = String::from("Processing");
|
||||
// TODO: FIXME
|
||||
}
|
||||
Mode::ScanWinImages => {
|
||||
select_type = SelectionType::Loop;
|
||||
title = String::from("Scanning");
|
||||
// TODO: FIXME
|
||||
}
|
||||
Mode::SelectWinImage => {
|
||||
select_type = SelectionType::One;
|
||||
title = String::from("Select Windows Image");
|
||||
// TODO: FIXME, I think this whole section could be better...
|
||||
}
|
||||
Mode::SelectDisks => {
|
||||
select_type = SelectionType::One;
|
||||
title = String::from("Select Destination Disk");
|
||||
let disk_list = app.state.disk_list.lock().unwrap();
|
||||
disk_list
|
||||
.iter()
|
||||
.for_each(|disk| items.push(disk.description.to_string()));
|
||||
}
|
||||
Mode::SelectTableType => {
|
||||
select_type = SelectionType::One;
|
||||
title = String::from("Select Partition Table Type");
|
||||
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");
|
||||
}
|
||||
Mode::ScanDisks | Mode::PreClone | Mode::Clone | Mode::PostClone => {
|
||||
select_type = SelectionType::Loop;
|
||||
title = String::from("Processing");
|
||||
}
|
||||
Mode::Done | Mode::Failed => {
|
||||
select_type = SelectionType::Loop;
|
||||
title = String::from("Done");
|
||||
}
|
||||
// Invalid states
|
||||
Mode::BootDiags
|
||||
| Mode::BootScan
|
||||
| Mode::BootSetup
|
||||
| Mode::DiagMenu
|
||||
| Mode::InjectDrivers
|
||||
| Mode::LogView
|
||||
| Mode::PEMenu
|
||||
| Mode::SelectParts
|
||||
| Mode::SetBootMode => panic!("This shouldn't happen?"),
|
||||
};
|
||||
Action::UpdateLeft(title, labels, items, select_type)
|
||||
}
|
||||
|
||||
fn build_right_items(app: &App) -> Action {
|
||||
let mut items = Vec::new();
|
||||
let mut labels: Vec<Vec<DVLine>> = Vec::new();
|
||||
let mut start_index = 0;
|
||||
match app.cur_mode {
|
||||
Mode::InstallDrivers => {
|
||||
items.push(vec![DVLine {
|
||||
line_parts: vec![String::from("CPU")],
|
||||
line_colors: vec![Color::Cyan],
|
||||
}]);
|
||||
items.push(vec![DVLine {
|
||||
line_parts: vec![get_cpu_name()],
|
||||
line_colors: vec![Color::Reset],
|
||||
}]);
|
||||
start_index = 2;
|
||||
}
|
||||
Mode::SelectDisks => {
|
||||
// Labels
|
||||
let dest_dv_line = DVLine {
|
||||
line_parts: vec![
|
||||
String::from("Dest"),
|
||||
String::from(" (WARNING: ALL DATA WILL BE DELETED!)"),
|
||||
],
|
||||
line_colors: vec![Color::Cyan, Color::Red],
|
||||
};
|
||||
if let Some(table_type) = &app.state.table_type {
|
||||
// Show table type
|
||||
let type_str = match table_type {
|
||||
PartitionTableType::Guid => "GPT",
|
||||
PartitionTableType::Legacy => "MBR",
|
||||
};
|
||||
labels.push(vec![
|
||||
dest_dv_line,
|
||||
DVLine {
|
||||
line_parts: vec![format!(" (Will be formatted {type_str})")],
|
||||
line_colors: vec![Color::Yellow],
|
||||
},
|
||||
]);
|
||||
} else {
|
||||
labels.push(vec![dest_dv_line]);
|
||||
}
|
||||
let disk_list = app.state.disk_list.lock().unwrap();
|
||||
disk_list
|
||||
.iter()
|
||||
.for_each(|disk| items.push(get_disk_description_right(disk, &None)));
|
||||
}
|
||||
Mode::SelectTableType | Mode::SelectWinImage | Mode::SetUserName | Mode::Confirm => {
|
||||
// Labels
|
||||
let dest_dv_line = DVLine {
|
||||
line_parts: vec![
|
||||
String::from("Dest"),
|
||||
String::from(" (WARNING: ALL DATA WILL BE DELETED!)"),
|
||||
],
|
||||
line_colors: vec![Color::Cyan, Color::Red],
|
||||
};
|
||||
if let Some(table_type) = &app.state.table_type {
|
||||
// Show table type
|
||||
let type_str = match table_type {
|
||||
PartitionTableType::Guid => "GPT",
|
||||
PartitionTableType::Legacy => "MBR",
|
||||
};
|
||||
labels.push(vec![
|
||||
dest_dv_line,
|
||||
DVLine {
|
||||
line_parts: vec![format!(" (Will be formatted {type_str})")],
|
||||
line_colors: vec![Color::Yellow],
|
||||
},
|
||||
]);
|
||||
} else {
|
||||
labels.push(vec![dest_dv_line]);
|
||||
}
|
||||
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)
|
||||
{
|
||||
items.push(get_disk_description_right(disk, &None));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Action::UpdateRight(labels, start_index, items)
|
||||
}
|
||||
|
||||
fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
|
||||
// Cut the given rectangle into three vertical pieces
|
||||
let popup_layout = Layout::default()
|
||||
|
|
@ -328,16 +632,20 @@ fn get_chunks(r: Rect) -> Vec<Rect> {
|
|||
.split(r)
|
||||
.to_vec(),
|
||||
);
|
||||
let center = centered_rect(90, 90, chunks[1]);
|
||||
|
||||
// Left/Right
|
||||
chunks.extend(
|
||||
Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
|
||||
.split(centered_rect(90, 90, chunks[1]))
|
||||
.split(center)
|
||||
.to_vec(),
|
||||
);
|
||||
|
||||
// Center
|
||||
chunks.push(center);
|
||||
|
||||
// Popup
|
||||
chunks.push(centered_rect(60, 25, r));
|
||||
|
||||
|
|
|
|||
16
win_installer/src/components.rs
Normal file
16
win_installer/src/components.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
// 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/>.
|
||||
//
|
||||
pub mod wim_scan;
|
||||
186
win_installer/src/components/wim_scan.rs
Normal file
186
win_installer/src/components/wim_scan.rs
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
// 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 core::{action::Action, components::Component, config::Config, state::Mode};
|
||||
use std::{
|
||||
iter::zip,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::KeyEvent;
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
widgets::{Block, Borders, Clear, HighlightSpacing, List, ListItem, Padding, Paragraph},
|
||||
};
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
|
||||
use crate::wim::WimSources;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct WimScan {
|
||||
command_tx: Option<UnboundedSender<Action>>,
|
||||
config: Config,
|
||||
mode: Mode,
|
||||
scan_network: bool,
|
||||
wim_sources: Arc<Mutex<WimSources>>,
|
||||
}
|
||||
|
||||
impl WimScan {
|
||||
#[must_use]
|
||||
pub fn new(wim_sources: Arc<Mutex<WimSources>>) -> Self {
|
||||
let wim_sources = wim_sources.clone();
|
||||
Self {
|
||||
wim_sources,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for WimScan {
|
||||
fn handle_key_event(&mut self, key: KeyEvent) -> Result<Option<Action>> {
|
||||
let _ = key; // to appease clippy
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
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 update(&mut self, action: Action) -> Result<Option<Action>> {
|
||||
match action {
|
||||
Action::FindWimNetwork => self.scan_network = true,
|
||||
Action::SetMode(new_mode) => {
|
||||
self.mode = new_mode;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
|
||||
if self.mode != Mode::ScanWinImages {
|
||||
return Ok(());
|
||||
}
|
||||
frame.render_widget(Clear, area);
|
||||
|
||||
// Prep
|
||||
let [left, right] = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
|
||||
.areas(area);
|
||||
let [left_title, left_body] = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Length(1), Constraint::Min(1)])
|
||||
.areas(left);
|
||||
let [right_title, right_body] = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Length(1), Constraint::Min(1)])
|
||||
.areas(right);
|
||||
let [local_area, network_area] = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
|
||||
.areas(left_body);
|
||||
|
||||
// Titles
|
||||
let titles = vec![
|
||||
Paragraph::new(Line::from("Scanning").centered())
|
||||
.block(Block::default().borders(Borders::NONE)),
|
||||
Paragraph::new(Line::from("Info").centered())
|
||||
.block(Block::default().borders(Borders::NONE)),
|
||||
];
|
||||
for (title, area) in zip(titles, [left_title, right_title]) {
|
||||
frame.render_widget(title, area);
|
||||
}
|
||||
|
||||
// Local Scan
|
||||
let local_area = if self.scan_network {
|
||||
local_area
|
||||
} else {
|
||||
left_body
|
||||
};
|
||||
let local_words = "Lorem ipsum dolor sit amet".split_whitespace().to_owned();
|
||||
let local_list = List::new(local_words)
|
||||
.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(local_list, local_area);
|
||||
|
||||
// Network Scan
|
||||
if self.scan_network {
|
||||
let network_words = "Adipiscing elit quisque faucibus ex sapien"
|
||||
.split_whitespace()
|
||||
.to_owned();
|
||||
let network_list = List::new(network_words)
|
||||
.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(network_list, network_area);
|
||||
}
|
||||
|
||||
// WIM Info
|
||||
let wim_sources = self.wim_sources.lock().unwrap();
|
||||
let mut right_list = Vec::new();
|
||||
if !wim_sources.network.is_empty() {
|
||||
right_list.push(ListItem::new("-- Network --\n\n"));
|
||||
right_list.extend(
|
||||
wim_sources
|
||||
.network
|
||||
.iter()
|
||||
.map(|wimfile| ListItem::new(format!("{}\n\n", wimfile.path))),
|
||||
);
|
||||
right_list.push(ListItem::new(""));
|
||||
}
|
||||
right_list.push(ListItem::new("-- Local --\n\n"));
|
||||
right_list.extend(
|
||||
wim_sources
|
||||
.local
|
||||
.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);
|
||||
|
||||
// Done
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ use color_eyre::Result;
|
|||
use crate::app::App;
|
||||
|
||||
mod app;
|
||||
mod components;
|
||||
mod state;
|
||||
mod wim;
|
||||
|
||||
|
|
|
|||
|
|
@ -16,23 +16,26 @@
|
|||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use core::system::{disk::Disk, drivers};
|
||||
use core::system::{
|
||||
disk::{Disk, PartitionTableType},
|
||||
drivers,
|
||||
};
|
||||
|
||||
use crate::wim::WimSources;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct State<'a> {
|
||||
pub struct State {
|
||||
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 part_index_boot: Option<usize>,
|
||||
pub table_type: Option<PartitionTableType>,
|
||||
pub wim_file_index: Option<usize>,
|
||||
pub wim_image_index: Option<usize>,
|
||||
pub wim_sources: Arc<Mutex<WimSources<'a>>>,
|
||||
pub wim_sources: Arc<Mutex<WimSources>>,
|
||||
}
|
||||
|
||||
impl State<'_> {
|
||||
impl State {
|
||||
pub fn new(disk_list: Arc<Mutex<Vec<Disk>>>) -> Self {
|
||||
State {
|
||||
disk_list,
|
||||
|
|
@ -40,13 +43,18 @@ impl State<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
pub fn reset_all(&mut self) {
|
||||
self.disk_index_dest = None;
|
||||
self.part_index_boot = None;
|
||||
self.wim_file_index = None;
|
||||
self.wim_image_index = None;
|
||||
if let Ok(mut sources) = self.wim_sources.lock() {
|
||||
sources.reset();
|
||||
sources.reset_all();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset_network(&mut self) {
|
||||
if let Ok(mut sources) = self.wim_sources.lock() {
|
||||
sources.reset_network();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,18 +48,18 @@ static WIN_BUILDS: LazyLock<HashMap<&str, &str>> = LazyLock::new(|| {
|
|||
});
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WimFile<'a> {
|
||||
path: &'a Path,
|
||||
images: Vec<WimImage>,
|
||||
pub struct WimFile {
|
||||
pub path: String,
|
||||
pub images: Vec<WimImage>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct WimImage {
|
||||
build: String,
|
||||
index: String,
|
||||
name: String,
|
||||
spbuild: String,
|
||||
version: String,
|
||||
pub build: String,
|
||||
pub index: String,
|
||||
pub name: String,
|
||||
pub spbuild: String,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
impl WimImage {
|
||||
|
|
@ -89,20 +89,24 @@ impl fmt::Display for WimImage {
|
|||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct WimSources<'a> {
|
||||
pub local: Vec<WimFile<'a>>,
|
||||
pub network: Vec<WimFile<'a>>,
|
||||
pub struct WimSources {
|
||||
pub local: Vec<WimFile>,
|
||||
pub network: Vec<WimFile>,
|
||||
}
|
||||
|
||||
impl WimSources<'_> {
|
||||
impl WimSources {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
pub fn reset_all(&mut self) {
|
||||
self.local.clear();
|
||||
self.network.clear();
|
||||
}
|
||||
|
||||
pub fn reset_network(&mut self) {
|
||||
self.network.clear();
|
||||
}
|
||||
}
|
||||
|
||||
fn get_wim_xml(wim_file: &str) -> std::io::Result<File> {
|
||||
|
|
@ -120,10 +124,9 @@ fn get_wim_xml(wim_file: &str) -> std::io::Result<File> {
|
|||
Ok(file)
|
||||
}
|
||||
|
||||
fn parse_wim_file(wim_file: &str) -> std::io::Result<WimFile<'_>> {
|
||||
pub fn parse_wim_file(wim_file: &str) -> std::io::Result<WimFile> {
|
||||
let mut wim_images: Vec<WimImage> = Vec::new();
|
||||
let wim_path = Path::new(wim_file);
|
||||
if !wim_path.exists() {
|
||||
if !Path::new(wim_file).exists() {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
"Failed to read WIM file",
|
||||
|
|
@ -142,7 +145,6 @@ fn parse_wim_file(wim_file: &str) -> std::io::Result<WimFile<'_>> {
|
|||
Ok(XmlEvent::StartElement {
|
||||
name, attributes, ..
|
||||
}) => {
|
||||
println!("{:spaces$}+{name}", "", spaces = depth * 2);
|
||||
depth += 1;
|
||||
current_element = name.local_name.to_uppercase();
|
||||
|
||||
|
|
@ -160,20 +162,16 @@ fn parse_wim_file(wim_file: &str) -> std::io::Result<WimFile<'_>> {
|
|||
let build = char_data.trim();
|
||||
image.build = build.to_string();
|
||||
image.version = WIN_BUILDS.get(build).map_or("", |v| v).to_string();
|
||||
println!("{:spaces$} {char_data}", "", spaces = (depth + 1) * 2);
|
||||
}
|
||||
if current_element == "NAME" {
|
||||
image.name = char_data.trim().to_string();
|
||||
println!("{:spaces$} {char_data}", "", spaces = (depth + 1) * 2);
|
||||
}
|
||||
if current_element == "SPBUILD" {
|
||||
image.spbuild = char_data.trim().to_string();
|
||||
println!("{:spaces$} {char_data}", "", spaces = (depth + 1) * 2);
|
||||
}
|
||||
}
|
||||
Ok(XmlEvent::EndElement { name }) => {
|
||||
depth -= 1;
|
||||
println!("{:spaces$}-{name}", "", spaces = depth * 2);
|
||||
|
||||
if name.local_name.to_uppercase() == "IMAGE" {
|
||||
// Append image to list
|
||||
|
|
@ -187,14 +185,13 @@ fn parse_wim_file(wim_file: &str) -> std::io::Result<WimFile<'_>> {
|
|||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error: {e}");
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let wim_file = WimFile {
|
||||
path: wim_path,
|
||||
path: wim_file.to_string(),
|
||||
images: wim_images,
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue