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": {
"ScanDisks": {
"<Enter>": "Process",
"<q>": "Quit", // Quit the application
"<Ctrl-d>": "Quit", // 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
// 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 crossterm::event::KeyEvent;
@ -32,6 +36,7 @@ use crate::{
Component,
},
config::Config,
system::disk::{get_disks, Disk},
tui::{Event, Tui},
};
@ -40,6 +45,7 @@ pub struct App {
tick_rate: f64,
frame_rate: f64,
components: Vec<Box<dyn Component>>,
disks: Arc<Mutex<Vec<Disk>>>,
should_quit: bool,
should_suspend: bool,
cur_mode: Mode,
@ -74,6 +80,7 @@ impl App {
Box::new(Footer::new()),
Box::new(Popup::new()),
],
disks: Arc::new(Mutex::new(Vec::new())),
should_quit: false,
should_suspend: false,
config: Config::new()?,
@ -114,6 +121,8 @@ impl App {
}
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()?
// .mouse(true) // uncomment this line to enable mouse support
.tick_rate(self.tick_rate)
@ -203,6 +212,12 @@ impl App {
match action {
Action::Tick => {
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::Suspend => self.should_suspend = true,
@ -212,13 +227,17 @@ impl App {
Action::Render => self.render(tui)?,
Action::PrevScreen => {
self.action_tx.send(Action::SetMode(self.prev_mode))?;
self.action_tx.send(Action::Select(None, None))?;
}
Action::NextScreen => {
if let Some(mode) = self.next_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) => {
self.selections[0] = one;
self.selections[1] = two;
@ -228,8 +247,10 @@ impl App {
match new_mode {
Mode::ScanDisks => {
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 => {
self.action_tx.send(Action::DisplayPopup(String::from(
@ -330,3 +351,10 @@ fn get_chunks(r: Rect) -> Vec<Rect> {
// Done
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 {
pub fn new() -> 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()
}
}
@ -57,7 +57,7 @@ impl Component for Footer {
}
Action::SetMode(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(
"(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::KeyDown => self.item_list.next(),
Action::Process => match self.mode {
Mode::ScanDisks => {
if let Some(command_tx) = self.command_tx.clone() {
command_tx.send(Action::ScanDisks)?;
}
}
Mode::Confirm => {
if let Some(command_tx) = self.command_tx.clone() {
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::SetMode(new_mode) => {
let prev_mode = self.mode;
self.mode = 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 => {
self.selections[0] = 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 => {
self.selections[0] = None;
@ -171,7 +171,7 @@ impl Component for Left {
// Body (scan disks)
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()
.borders(Borders::ALL)
.padding(Padding::new(1, 1, 1, 1)),

View file

@ -1,4 +1,3 @@
use clap::command;
// This file is part of Deja-vu.
//
// 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 super::Component;
use crate::{
action::Action,
app::Mode,
config::Config,
system::disk::{get_disks, Disk},
};
use crate::{action::Action, app::Mode, config::Config};
#[derive(Default)]
pub struct Popup {
command_tx: Option<UnboundedSender<Action>>,
config: Config,
disks: Vec<Disk>,
popup_text: String,
}
@ -64,18 +57,7 @@ impl Component for Popup {
}
Action::DismissPopup => self.popup_text.clear(),
Action::DisplayPopup(new_text) => self.popup_text = String::from(new_text),
Action::ScanDisks => {
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()))?;
}
}
Action::SetMode(Mode::ScanDisks) => self.popup_text = String::from("Scanning Disks..."),
_ => {}
}
Ok(None)

View file

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

View file

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