Run get_disks() in the background using a thread

Allows app interaction while the scan is running
This commit is contained in:
2Shirt 2024-11-03 17:23:07 -08:00
parent 7d4f28f950
commit c62c6c751c
Signed by: 2Shirt
GPG key ID: 152FAC923B0E132C
7 changed files with 55 additions and 40 deletions

View file

@ -1,7 +1,6 @@
{ {
"keybindings": { "keybindings": {
"ScanDisks": { "ScanDisks": {
"<Enter>": "Process",
"<q>": "Quit", // Quit the application "<q>": "Quit", // Quit the application
"<Ctrl-d>": "Quit", // Another way to quit "<Ctrl-d>": "Quit", // Another way to quit
"<Ctrl-c>": "Quit", // Yet another way to quit "<Ctrl-c>": "Quit", // Yet another way to quit

View file

@ -13,7 +13,11 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Deja-vu. If not, see <https://www.gnu.org/licenses/>. // along with Deja-vu. If not, see <https://www.gnu.org/licenses/>.
// //
use std::iter::zip; use std::{
iter::zip,
sync::{Arc, Mutex},
thread::{self, JoinHandle},
};
use color_eyre::Result; use color_eyre::Result;
use crossterm::event::KeyEvent; use crossterm::event::KeyEvent;
@ -32,6 +36,7 @@ use crate::{
Component, Component,
}, },
config::Config, config::Config,
system::disk::{get_disks, Disk},
tui::{Event, Tui}, tui::{Event, Tui},
}; };
@ -40,6 +45,7 @@ pub struct App {
tick_rate: f64, tick_rate: f64,
frame_rate: f64, frame_rate: f64,
components: Vec<Box<dyn Component>>, components: Vec<Box<dyn Component>>,
disks: Arc<Mutex<Vec<Disk>>>,
should_quit: bool, should_quit: bool,
should_suspend: bool, should_suspend: bool,
cur_mode: Mode, cur_mode: Mode,
@ -74,6 +80,7 @@ impl App {
Box::new(Footer::new()), Box::new(Footer::new()),
Box::new(Popup::new()), Box::new(Popup::new()),
], ],
disks: Arc::new(Mutex::new(Vec::new())),
should_quit: false, should_quit: false,
should_suspend: false, should_suspend: false,
config: Config::new()?, config: Config::new()?,
@ -114,6 +121,8 @@ impl App {
} }
pub async fn run(&mut self) -> Result<()> { pub async fn run(&mut self) -> Result<()> {
let disk_wrapper = Arc::clone(&self.disks);
let _ = lazy_get_disks(disk_wrapper);
let mut tui = Tui::new()? let mut tui = Tui::new()?
// .mouse(true) // uncomment this line to enable mouse support // .mouse(true) // uncomment this line to enable mouse support
.tick_rate(self.tick_rate) .tick_rate(self.tick_rate)
@ -203,6 +212,12 @@ impl App {
match action { match action {
Action::Tick => { Action::Tick => {
self.last_tick_key_events.drain(..); self.last_tick_key_events.drain(..);
// Continue to next screen if shared disks has been set
if self.cur_mode == Mode::ScanDisks {
if let Ok(_) = &self.disks.try_lock() {
self.action_tx.send(Action::NextScreen)?;
}
}
} }
Action::Quit => self.should_quit = true, Action::Quit => self.should_quit = true,
Action::Suspend => self.should_suspend = true, Action::Suspend => self.should_suspend = true,
@ -212,13 +227,17 @@ impl App {
Action::Render => self.render(tui)?, Action::Render => self.render(tui)?,
Action::PrevScreen => { Action::PrevScreen => {
self.action_tx.send(Action::SetMode(self.prev_mode))?; self.action_tx.send(Action::SetMode(self.prev_mode))?;
self.action_tx.send(Action::Select(None, None))?;
} }
Action::NextScreen => { Action::NextScreen => {
if let Some(mode) = self.next_mode() { if let Some(mode) = self.next_mode() {
self.action_tx.send(Action::SetMode(mode))?; self.action_tx.send(Action::SetMode(mode))?;
} }
} }
Action::ScanDisks => {
let disk_wrapper = Arc::clone(&self.disks);
let _ = lazy_get_disks(disk_wrapper);
self.action_tx.send(Action::SetMode(Mode::ScanDisks))?;
}
Action::Select(one, two) => { Action::Select(one, two) => {
self.selections[0] = one; self.selections[0] = one;
self.selections[1] = two; self.selections[1] = two;
@ -228,8 +247,10 @@ impl App {
match new_mode { match new_mode {
Mode::ScanDisks => { Mode::ScanDisks => {
self.prev_mode = self.cur_mode; self.prev_mode = self.cur_mode;
self.action_tx.send(Action::Select(None, None))?; }
self.action_tx.send(Action::ScanDisks)?; Mode::SelectDisks | Mode::SelectParts => {
let disks = self.disks.lock().unwrap();
self.action_tx.send(Action::UpdateDiskList(disks.clone()))?;
} }
Mode::Done => { Mode::Done => {
self.action_tx.send(Action::DisplayPopup(String::from( self.action_tx.send(Action::DisplayPopup(String::from(
@ -330,3 +351,10 @@ fn get_chunks(r: Rect) -> Vec<Rect> {
// Done // Done
chunks chunks
} }
fn lazy_get_disks(disk_wrapper: Arc<Mutex<Vec<Disk>>>) -> JoinHandle<()> {
thread::spawn(move || {
let mut disks = disk_wrapper.lock().unwrap();
*disks = get_disks();
})
}

View file

@ -30,7 +30,7 @@ pub struct Footer {
impl Footer { impl Footer {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
footer_text: String::from("(Enter) to select / (b) to go back / (q) to quit"), footer_text: String::from("(q) to quit"),
..Default::default() ..Default::default()
} }
} }
@ -57,7 +57,7 @@ impl Component for Footer {
} }
Action::SetMode(new_mode) => { Action::SetMode(new_mode) => {
self.footer_text = match new_mode { self.footer_text = match new_mode {
Mode::ScanDisks => String::from("(Enter) to start / (q) to quit"), Mode::ScanDisks => String::from("(q) to quit"),
Mode::SelectDisks => String::from( Mode::SelectDisks => String::from(
"(Enter) to select / / (i) to install driver / (r) to rescan / (q) to quit", "(Enter) to select / / (i) to install driver / (r) to rescan / (q) to quit",
), ),

View file

@ -69,11 +69,6 @@ impl Component for Left {
Action::KeyUp => self.item_list.previous(), Action::KeyUp => self.item_list.previous(),
Action::KeyDown => self.item_list.next(), Action::KeyDown => self.item_list.next(),
Action::Process => match self.mode { Action::Process => match self.mode {
Mode::ScanDisks => {
if let Some(command_tx) = self.command_tx.clone() {
command_tx.send(Action::ScanDisks)?;
}
}
Mode::Confirm => { Mode::Confirm => {
if let Some(command_tx) = self.command_tx.clone() { if let Some(command_tx) = self.command_tx.clone() {
command_tx.send(Action::NextScreen)?; command_tx.send(Action::NextScreen)?;
@ -123,17 +118,22 @@ impl Component for Left {
} }
_ => {} _ => {}
}, },
Action::Select(None, None) => self.selections = vec![None, None],
Action::Select(Some(index), None) => self.selections[0] = Some(index), Action::Select(Some(index), None) => self.selections[0] = Some(index),
Action::SetMode(new_mode) => { Action::SetMode(new_mode) => {
let prev_mode = self.mode; let prev_mode = self.mode;
self.mode = new_mode; self.mode = new_mode;
match new_mode { match new_mode {
Mode::ScanDisks => self.title_text = String::from(""), Mode::ScanDisks => {
self.title_text = String::from("");
self.item_list.clear_items();
}
Mode::SelectDisks => { Mode::SelectDisks => {
self.selections[0] = None; self.selections[0] = None;
self.selections[1] = None; self.selections[1] = None;
self.title_text = String::from("Select Source and Destination Disks") self.title_text = String::from("Select Source and Destination Disks");
if let Some(command_tx) = self.command_tx.clone() {
command_tx.send(Action::DismissPopup)?;
}
} }
Mode::SelectParts => { Mode::SelectParts => {
self.selections[0] = None; self.selections[0] = None;
@ -171,7 +171,7 @@ impl Component for Left {
// Body (scan disks) // Body (scan disks)
if self.item_list.items.is_empty() { if self.item_list.items.is_empty() {
let paragraph = Paragraph::new(String::from("Press Enter to start!")).block( let paragraph = Paragraph::new(String::new()).block(
Block::default() Block::default()
.borders(Borders::ALL) .borders(Borders::ALL)
.padding(Padding::new(1, 1, 1, 1)), .padding(Padding::new(1, 1, 1, 1)),

View file

@ -1,4 +1,3 @@
use clap::command;
// This file is part of Deja-vu. // This file is part of Deja-vu.
// //
// Deja-vu is free software: you can redistribute it and/or modify it // Deja-vu is free software: you can redistribute it and/or modify it
@ -19,18 +18,12 @@ use ratatui::{prelude::*, widgets::*};
use tokio::sync::mpsc::UnboundedSender; use tokio::sync::mpsc::UnboundedSender;
use super::Component; use super::Component;
use crate::{ use crate::{action::Action, app::Mode, config::Config};
action::Action,
app::Mode,
config::Config,
system::disk::{get_disks, Disk},
};
#[derive(Default)] #[derive(Default)]
pub struct Popup { pub struct Popup {
command_tx: Option<UnboundedSender<Action>>, command_tx: Option<UnboundedSender<Action>>,
config: Config, config: Config,
disks: Vec<Disk>,
popup_text: String, popup_text: String,
} }
@ -64,18 +57,7 @@ impl Component for Popup {
} }
Action::DismissPopup => self.popup_text.clear(), Action::DismissPopup => self.popup_text.clear(),
Action::DisplayPopup(new_text) => self.popup_text = String::from(new_text), Action::DisplayPopup(new_text) => self.popup_text = String::from(new_text),
Action::ScanDisks => { Action::SetMode(Mode::ScanDisks) => self.popup_text = String::from("Scanning Disks..."),
if let Some(command_tx) = self.command_tx.clone() {
self.disks = get_disks();
command_tx.send(Action::NextScreen)?;
}
}
Action::SetMode(Mode::SelectDisks) => {
self.popup_text.clear();
if let Some(command_tx) = self.command_tx.clone() {
command_tx.send(Action::UpdateDiskList(self.disks.clone()))?;
}
}
_ => {} _ => {}
} }
Ok(None) Ok(None)

View file

@ -19,13 +19,14 @@ use ratatui::{prelude::*, widgets::*};
use tokio::sync::mpsc::UnboundedSender; use tokio::sync::mpsc::UnboundedSender;
use super::Component; use super::Component;
use crate::{action::Action, app::Mode, config::Config}; use crate::{action::Action, app::Mode, config::Config, system::disk::Disk};
#[derive(Default)] #[derive(Default)]
pub struct Right { pub struct Right {
command_tx: Option<UnboundedSender<Action>>, command_tx: Option<UnboundedSender<Action>>,
config: Config, config: Config,
cur_mode: Mode, cur_mode: Mode,
disks: Vec<Disk>,
prev_mode: Mode, prev_mode: Mode,
all_modes: Vec<Mode>, all_modes: Vec<Mode>,
selections: Vec<Option<usize>>, selections: Vec<Option<usize>>,
@ -76,6 +77,7 @@ impl Component for Right {
self.cur_mode = new_mode; self.cur_mode = new_mode;
self.all_modes.push(new_mode); self.all_modes.push(new_mode);
} }
Action::UpdateDiskList(disks) => self.disks = disks,
_ => {} _ => {}
} }
Ok(None) Ok(None)
@ -95,8 +97,12 @@ impl Component for Right {
// Body // Body
let paragraph = Paragraph::new(format!( let paragraph = Paragraph::new(format!(
"Prev Mode: {:?} // Cur Mode: {:?}\n{:?}\n{:?}", "Prev Mode: {:?} // Cur Mode: {:?}\n{:?}\n{:?}\nDisks: {}",
self.prev_mode, self.cur_mode, self.all_modes, self.selections, self.prev_mode,
self.cur_mode,
self.all_modes,
self.selections,
self.disks.len(),
)) ))
.block( .block(
Block::default() Block::default()

View file

@ -135,7 +135,7 @@ impl fmt::Display for PartitionTableType {
} }
pub fn get_disks() -> Vec<Disk> { pub fn get_disks() -> Vec<Disk> {
let disks; let disks: Vec<Disk>;
if cfg!(windows) { if cfg!(windows) {
disks = diskpart::get_disks(); disks = diskpart::get_disks();
} else { } else {