deja-vu/deja_vu/src/components/right.rs

401 lines
17 KiB
Rust

// 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 crossterm::event::KeyEvent;
use ratatui::{
prelude::*,
widgets::{Block, Borders, Padding, Paragraph, Wrap},
};
use tokio::sync::mpsc::UnboundedSender;
use tracing::info;
use super::{state::StatefulList, Component};
use crate::{
action::Action,
app::Mode,
config::Config,
system::{
cpu::get_cpu_name,
disk::{Disk, Partition, PartitionTableType},
},
};
#[derive(Default)]
pub struct Right {
command_tx: Option<UnboundedSender<Action>>,
config: Config,
cur_mode: Mode,
list_disks: StatefulList<Disk>,
list_parts: StatefulList<Partition>,
prev_mode: Mode,
selected_disks: Vec<Option<usize>>,
selections: Vec<Option<usize>>,
table_type: Option<PartitionTableType>,
}
impl Right {
pub fn new() -> Self {
Self {
selected_disks: vec![None, None],
selections: vec![None, None],
..Default::default()
}
}
}
impl Component for Right {
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::KeyUp => match self.cur_mode {
Mode::SelectDisks => self.list_disks.previous(),
Mode::SelectParts => self.list_parts.previous(),
_ => {}
},
Action::KeyDown => match self.cur_mode {
Mode::SelectDisks => self.list_disks.next(),
Mode::SelectParts => self.list_parts.next(),
_ => {}
},
Action::Process => {
if self.prev_mode == Mode::SelectDisks && self.cur_mode == Mode::Confirm {
self.selected_disks = self.selections.clone();
}
}
Action::Select(one, two) => {
self.selections[0] = one;
self.selections[1] = two;
}
Action::SelectTableType(table_type) => self.table_type = Some(table_type),
Action::SetMode(new_mode) => {
self.prev_mode = self.cur_mode;
self.cur_mode = new_mode;
match self.cur_mode {
Mode::SelectDisks => {
self.selections[0] = None;
self.selections[1] = None;
self.selected_disks[0] = None;
self.selected_disks[1] = None;
}
Mode::SelectParts => {
self.selections[0] = None;
self.selections[1] = None;
}
Mode::SelectTableType => {
self.selections[0] = None;
self.selections[1] = None;
self.table_type = None;
}
_ => {}
}
}
Action::UpdateDiskList(disks) => {
info!("Updating disk list");
self.list_disks.set_items(disks);
if self.cur_mode == Mode::Clone {
if let Some(index) = self.selected_disks[1] {
if let Some(disk) = self.list_disks.get(index) {
self.list_parts.set_items(disk.get_parts());
// Auto-select first partition and highlight likely OS partition
if let Some(index) = self.selected_disks[1] {
if let Some(disk) = self.list_disks.get(index) {
if let Some(table_type) = &self.table_type {
match table_type {
PartitionTableType::Guid => {
if disk.num_parts() >= 3 {
self.selections[0] = Some(0);
self.list_parts.select(2);
}
}
PartitionTableType::Legacy => {
if disk.num_parts() >= 2 {
self.selections[0] = Some(0);
self.list_parts.select(1);
}
}
}
}
}
}
}
}
}
}
_ => {}
}
Ok(None)
}
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
let [title_area, body_area] = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Length(1), Constraint::Min(1)])
.areas(area);
// Title
let title_text = String::from("Info");
let title = Paragraph::new(Line::from(title_text).centered())
.block(Block::default().borders(Borders::NONE));
frame.render_widget(title, title_area);
// Body
let mut body_text = Vec::new();
match (self.prev_mode, self.cur_mode) {
(_, Mode::InstallDrivers) => {
body_text.push(Line::from(Span::raw(format!("CPU: {}", get_cpu_name()))));
}
(_, Mode::SelectDisks | Mode::SelectTableType)
| (Mode::SelectDisks | Mode::SelectTableType, Mode::Confirm) => {
// Source Disk
body_text.push(Line::from(Span::styled(
"Source:",
Style::default().cyan().bold(),
)));
body_text.push(Line::from(""));
body_text.push(Line::from(Span::styled(
format!(
"{:<8} {:>11} {:<4} {:<4} {}",
"Disk ID", "Size", "Conn", "Type", "Model (Serial)"
),
Style::new().green().bold(),
)));
let index = if self.selected_disks[0].is_some() {
// Selected in prior mode
self.selected_disks[0]
} else if self.selections[0].is_some() {
// Selected in this mode
self.selections[0]
} else {
// Highlighted entry
self.list_disks.selected()
};
if let Some(i) = index {
if let Some(disk) = self.list_disks.get(i) {
body_text.push(Line::from(Span::raw(&disk.description)));
// Source parts
body_text.push(Line::from(""));
body_text.push(Line::from(Span::styled(
format!(
"{:<8} {:>11} {:<7} {}",
"Part ID", "Size", "(FS)", "\"Label\""
),
Style::new().blue().bold(),
)));
for line in &disk.parts_description {
body_text.push(Line::from(Span::raw(line)));
}
}
}
// Destination Disk
let index = if self.selected_disks[1].is_some() {
// Selected in prior mode
self.selected_disks[1]
} else {
// Select(ed) in this mode
match (self.selections[0], self.selections[1]) {
(Some(one), None) => {
// First selected
if let Some(two) = self.selections[1] {
if one == two {
None
} else {
self.selections[1]
}
} else {
self.list_disks.selected()
}
}
(Some(_), Some(_)) => {
// Both selected
self.selections[1]
}
(_, _) => None,
}
};
if let Some(i) = index {
// Divider
body_text.push(Line::from(""));
body_text.push(Line::from(str::repeat("", (body_area.width - 4) as usize)));
body_text.push(Line::from(""));
// Disk
if let Some(disk) = self.list_disks.get(i) {
body_text.push(Line::from(vec![
Span::styled("Dest:", Style::default().cyan().bold()),
Span::styled(
" (WARNING: ALL DATA WILL BE DELETED!)",
Style::default().red().bold(),
),
]));
if let Some(table_type) = &self.table_type {
body_text.push(Line::from(Span::styled(
format!(" (Will be formatted {table_type})"),
Style::default().yellow().bold(),
)));
}
body_text.push(Line::from(""));
body_text.push(Line::from(Span::styled(
format!(
"{:<8} {:>11} {:<4} {:<4} {}",
"Disk ID", "Size", "Conn", "Type", "Model (Serial)"
),
Style::new().green().bold(),
)));
body_text.push(Line::from(Span::raw(&disk.description)));
// Destination parts
body_text.push(Line::from(""));
body_text.push(Line::from(Span::styled(
format!(
"{:<8} {:>11} {:<7} {}",
"Part ID", "Size", "(FS)", "\"Label\""
),
Style::new().blue().bold(),
)));
for line in &disk.parts_description {
body_text.push(Line::from(Span::raw(line)));
}
}
}
}
(_, Mode::SelectParts) | (Mode::SelectParts, Mode::Confirm) => {
// Disk
if let Some(index) = self.selected_disks[1] {
if let Some(disk) = self.list_disks.get(index) {
body_text.push(Line::from(Span::styled(
"Dest:",
Style::default().cyan().bold(),
)));
body_text.push(Line::from(""));
body_text.push(Line::from(Span::styled(
format!(
"{:<8} {:>11} {:<4} {:<4} {}",
"Disk ID", "Size", "Conn", "Type", "Model (Serial)"
),
Style::new().green().bold(),
)));
body_text.push(Line::from(Span::raw(&disk.description)));
// Destination parts
body_text.push(Line::from(""));
body_text.push(Line::from(Span::styled(
format!(
"{:<8} {:>11} {:<7} {}",
"Part ID", "Size", "(FS)", "\"Label\""
),
Style::new().blue().bold(),
)));
for line in &disk.parts_description {
body_text.push(Line::from(Span::raw(line)));
}
}
// Divider
body_text.push(Line::from(""));
body_text.push(Line::from(str::repeat("", (body_area.width - 4) as usize)));
body_text.push(Line::from(""));
// Boot Partition
// i.e. either the previously selected part or the highlighted one (if possible)
let mut boot_index = self.selections[0];
if boot_index.is_none() {
if let Some(i) = self.list_parts.selected() {
boot_index = Some(i);
}
}
if let Some(i) = boot_index {
if let Some(part) = self.list_parts.get(i) {
body_text.push(Line::from(Span::styled(
"Boot:",
Style::default().cyan().bold(),
)));
body_text.push(Line::from(""));
body_text.push(Line::from(Span::styled(
format!(
"{:<8} {:>11} {:<7} {}",
"Part ID", "Size", "(FS)", "\"Label\""
),
Style::new().blue().bold(),
)));
body_text.push(Line::from(Span::raw(format!("{part}"))));
}
}
// OS Partition
// i.e. either the previously selected part or the highlighted one (if needed)
let mut os_index = self.selections[1];
if os_index.is_none() {
if let Some(boot_index) = self.selections[0] {
if let Some(i) = self.list_parts.selected() {
if boot_index != i {
os_index = Some(i);
}
}
}
}
if let Some(i) = os_index {
if let Some(part) = self.list_parts.get(i) {
body_text.push(Line::from(""));
body_text.push(Line::from(Span::styled(
"OS:",
Style::default().cyan().bold(),
)));
body_text.push(Line::from(""));
body_text.push(Line::from(Span::styled(
format!(
"{:<8} {:>11} {:<7} {}",
"Part ID", "Size", "(FS)", "\"Label\""
),
Style::new().blue().bold(),
)));
body_text.push(Line::from(Span::raw(format!("{part}"))));
}
}
}
}
_ => {}
}
let body = Paragraph::new(body_text)
.style(Style::default().fg(Color::Gray))
.wrap(Wrap { trim: false })
.block(
Block::default()
.borders(Borders::ALL)
.padding(Padding::new(1, 1, 1, 1)),
);
frame.render_widget(body, body_area);
// Done
Ok(())
}
}