diff options
Diffstat (limited to 'src/interactive')
-rw-r--r-- | src/interactive/app/app_state.rs | 72 | ||||
-rw-r--r-- | src/interactive/app/eventloop.rs | 282 | ||||
-rw-r--r-- | src/interactive/app/terminal_app.rs | 63 | ||||
-rw-r--r-- | src/interactive/app/tests/utils.rs | 7 |
4 files changed, 70 insertions, 354 deletions
diff --git a/src/interactive/app/app_state.rs b/src/interactive/app/app_state.rs index 8d245db..14164d2 100644 --- a/src/interactive/app/app_state.rs +++ b/src/interactive/app/app_state.rs @@ -2,7 +2,7 @@ use std::time::Duration; use dua::{ inodefilter::InodeFilter, - traverse::{Tree, TreeIndex}, + traverse::{RunningTraversal, Tree, TreeIndex}, Throttle, WalkResult, }; use petgraph::Direction; @@ -34,76 +34,8 @@ pub struct AppState { pub message: Option<String>, pub focussed: FocussedPane, pub is_scanning: bool, - pub traversal_state: TraversalState, -} - -#[derive(Default)] -pub struct TraversalState { - pub previous_node_idx: TreeIndex, - pub parent_node_idx: TreeIndex, - pub directory_info_per_depth_level: Vec<EntryInfo>, - pub current_directory_at_depth: EntryInfo, - pub previous_depth: usize, - pub inodes: InodeFilter, - pub throttle: Option<Throttle>, pub received_event: bool, -} - -impl TraversalState { - pub fn new(root_idx: TreeIndex) -> Self { - Self { - previous_node_idx: root_idx, - parent_node_idx: root_idx, - directory_info_per_depth_level: Vec::new(), - current_directory_at_depth: EntryInfo::default(), - previous_depth: 0, - inodes: InodeFilter::default(), - throttle: Some(Throttle::new(Duration::from_millis(250), None)), - received_event: false, - } - } -} - -#[derive(Default, Copy, Clone)] -pub struct EntryInfo { - pub size: u128, - pub entries_count: Option<u64>, -} - -impl EntryInfo { - pub fn add_count(&mut self, other: &Self) { - self.entries_count = match (self.entries_count, other.entries_count) { - (Some(a), Some(b)) => Some(a + b), - (None, Some(b)) => Some(b), - (Some(a), None) => Some(a), - (None, None) => None, - }; - } -} - -pub fn set_entry_info_or_panic( - tree: &mut Tree, - node_idx: TreeIndex, - EntryInfo { - size, - entries_count, - }: EntryInfo, -) { - let node = tree - .node_weight_mut(node_idx) - .expect("node for parent index we just retrieved"); - node.size = size; - node.entry_count = entries_count; -} - -pub fn parent_or_panic(tree: &mut Tree, parent_node_idx: TreeIndex) -> TreeIndex { - tree.neighbors_directed(parent_node_idx, Direction::Incoming) - .next() - .expect("every node in the iteration has a parent") -} - -pub fn pop_or_panic(v: &mut Vec<EntryInfo>) -> EntryInfo { - v.pop().expect("sizes per level to be in sync with graph") + pub running_traversal: Option<RunningTraversal>, } pub enum ProcessingResult { diff --git a/src/interactive/app/eventloop.rs b/src/interactive/app/eventloop.rs index efb4cd1..594164d 100644 --- a/src/interactive/app/eventloop.rs +++ b/src/interactive/app/eventloop.rs @@ -9,23 +9,22 @@ use crate::{ }, }; use anyhow::Result; -use crossbeam::channel::Receiver; +use crossbeam::channel::{Receiver, Select}; use crosstermion::crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers}; use crosstermion::input::Event; use dua::{ - traverse::{size_on_disk, EntryData, Traversal}, + traverse::{size_on_disk, EntryData, ProcessEventResult, RunningTraversal, Traversal}, WalkOptions, WalkResult, }; -use std::time::{SystemTime, UNIX_EPOCH}; +use std::{ + path::PathBuf, + time::{SystemTime, UNIX_EPOCH}, +}; use tui::backend::Backend; use tui_react::Terminal; use super::app_state::{AppState, Cursor, ProcessingResult}; -use super::{ - app_state::{parent_or_panic, pop_or_panic, set_entry_info_or_panic, EntryInfo}, - terminal_app::TraversalEvent, - tree_view::TreeView, -}; +use super::tree_view::TreeView; impl AppState { pub fn navigation_mut(&mut self) -> &mut Navigation { @@ -71,251 +70,92 @@ impl AppState { result } + pub fn traverse( + &mut self, + traversal: &Traversal, + walk_options: &WalkOptions, + input: Vec<PathBuf>, + ) -> Result<()> { + let running_traversal = RunningTraversal::new(traversal.root_index, walk_options, input)?; + self.running_traversal = Some(running_traversal); + Ok(()) + } + + fn refresh_screen<B>( + &mut self, + window: &mut MainWindow, + traversal: &mut Traversal, + display: &mut DisplayOptions, + terminal: &mut Terminal<B>, + ) -> Result<()> + where + B: Backend, + { + let tree_view = self.tree_view(traversal); + self.draw(window, &tree_view, *display, terminal)?; + Ok(()) + } + pub fn process_events<B>( &mut self, window: &mut MainWindow, traversal: &mut Traversal, display: &mut DisplayOptions, terminal: &mut Terminal<B>, - walk_options: &WalkOptions, events: Receiver<Event>, - traversal_events: Receiver<TraversalEvent>, ) -> Result<ProcessingResult> where B: Backend, { - { - let tree_view = self.tree_view(traversal); - self.draw(window, &tree_view, *display, terminal)?; - } + self.refresh_screen(window, traversal, display, terminal)?; loop { - crossbeam::select! { - recv(events) -> event => { - let Ok(event) = event else { - continue; - }; - let result = self.process_event( - window, - traversal, - display, - terminal, - event)?; - if let Some(processing_result) = result { - return Ok(processing_result); - } - }, - recv(traversal_events) -> event => { - let Ok(event) = event else { - continue; - }; - if self.process_traversal_event(traversal, walk_options, event) { - self.update_state(traversal); + if let Some(running_traversal) = &mut self.running_traversal { + crossbeam::select! { + recv(events) -> event => { + let Ok(event) = event else { + continue; + }; let result = self.process_event( window, traversal, display, terminal, - Event::Key(refresh_key()))?; + event)?; if let Some(processing_result) = result { return Ok(processing_result); } - } - } - } - } - } - - fn process_traversal_event<'a>( - &mut self, - t: &'a mut Traversal, - walk_options: &'a WalkOptions, - event: TraversalEvent, - ) -> bool { - match event { - TraversalEvent::Entry(entry, root_path, device_id) => { - t.entries_traversed += 1; - let mut data = EntryData::default(); - match entry { - Ok(entry) => { - data.name = if entry.depth < 1 { - (*root_path).clone() - } else { - entry.file_name.into() + }, + recv(&running_traversal.event_rx) -> event => { + let Ok(event) = event else { + continue; }; - let mut file_size = 0u128; - let mut mtime: SystemTime = UNIX_EPOCH; - match &entry.client_state { - Some(Ok(ref m)) => { - if !m.is_dir() - && (walk_options.count_hard_links - || self.traversal_state.inodes.add(m)) - && (walk_options.cross_filesystems - || crossdev::is_same_device(device_id, m)) - { - if walk_options.apparent_size { - file_size = m.len() as u128; - } else { - file_size = size_on_disk(&entry.parent_path, &data.name, m) - .unwrap_or_else(|_| { - t.io_errors += 1; - data.metadata_io_error = true; - 0 - }) - as u128; - } - } else { - data.entry_count = Some(0); - data.is_dir = true; - } - - match m.modified() { - Ok(modified) => { - mtime = modified; - } - Err(_) => { - t.io_errors += 1; - data.metadata_io_error = true; - } - } - } - Some(Err(_)) => { - t.io_errors += 1; - data.metadata_io_error = true; + let result = running_traversal.process_event(traversal, event); + if result != ProcessEventResult::NoOp { + if result == ProcessEventResult::Finished { + self.is_scanning = false; + self.running_traversal = None; } - None => {} + self.update_state(traversal); + self.refresh_screen(window, traversal, display, terminal)?; } - - match (entry.depth, self.traversal_state.previous_depth) { - (n, p) if n > p => { - self.traversal_state - .directory_info_per_depth_level - .push(self.traversal_state.current_directory_at_depth); - self.traversal_state.current_directory_at_depth = EntryInfo { - size: file_size, - entries_count: Some(1), - }; - self.traversal_state.parent_node_idx = - self.traversal_state.previous_node_idx; - } - (n, p) if n < p => { - for _ in n..p { - set_entry_info_or_panic( - &mut t.tree, - self.traversal_state.parent_node_idx, - self.traversal_state.current_directory_at_depth, - ); - let dir_info = pop_or_panic( - &mut self.traversal_state.directory_info_per_depth_level, - ); - - self.traversal_state.current_directory_at_depth.size += - dir_info.size; - self.traversal_state - .current_directory_at_depth - .add_count(&dir_info); - - self.traversal_state.parent_node_idx = parent_or_panic( - &mut t.tree, - self.traversal_state.parent_node_idx, - ); - } - self.traversal_state.current_directory_at_depth.size += file_size; - *self - .traversal_state - .current_directory_at_depth - .entries_count - .get_or_insert(0) += 1; - set_entry_info_or_panic( - &mut t.tree, - self.traversal_state.parent_node_idx, - self.traversal_state.current_directory_at_depth, - ); - } - _ => { - self.traversal_state.current_directory_at_depth.size += file_size; - *self - .traversal_state - .current_directory_at_depth - .entries_count - .get_or_insert(0) += 1; - } - }; - - data.mtime = mtime; - data.size = file_size; - let entry_index = t.tree.add_node(data); - - t.tree - .add_edge(self.traversal_state.parent_node_idx, entry_index, ()); - self.traversal_state.previous_node_idx = entry_index; - self.traversal_state.previous_depth = entry.depth; - } - Err(_) => { - if self.traversal_state.previous_depth == 0 { - data.name = (*root_path).clone(); - let entry_index = t.tree.add_node(data); - t.tree - .add_edge(self.traversal_state.parent_node_idx, entry_index, ()); - } - - t.io_errors += 1 - } - } - - if let Some(throttle) = &self.traversal_state.throttle { - if throttle.can_update() { - return true; } } - } - TraversalEvent::Finished(io_errors) => { - t.io_errors += io_errors; - - self.traversal_state.throttle = None; - self.traversal_state - .directory_info_per_depth_level - .push(self.traversal_state.current_directory_at_depth); - self.traversal_state.current_directory_at_depth = EntryInfo::default(); - for _ in 0..self.traversal_state.previous_depth { - let dir_info = - pop_or_panic(&mut self.traversal_state.directory_info_per_depth_level); - self.traversal_state.current_directory_at_depth.size += dir_info.size; - self.traversal_state - .current_directory_at_depth - .add_count(&dir_info); - - set_entry_info_or_panic( - &mut t.tree, - self.traversal_state.parent_node_idx, - self.traversal_state.current_directory_at_depth, - ); - self.traversal_state.parent_node_idx = - parent_or_panic(&mut t.tree, self.traversal_state.parent_node_idx); + } else { + let Ok(event) = events.recv() else { + continue; + }; + let result = self.process_event(window, traversal, display, terminal, event)?; + if let Some(processing_result) = result { + return Ok(processing_result); } - let root_size = t.recompute_root_size(); - set_entry_info_or_panic( - &mut t.tree, - t.root_index, - EntryInfo { - size: root_size, - entries_count: (t.entries_traversed > 0).then_some(t.entries_traversed), - }, - ); - t.total_bytes = Some(root_size); - t.elapsed = Some(t.start.elapsed()); - - self.is_scanning = false; - - return true; } } - false } fn update_state(&mut self, traversal: &Traversal) { - let received_events = self.traversal_state.received_event; + let received_events = self.received_event; if !received_events { self.navigation_mut().view_root = traversal.root_index; } @@ -348,7 +188,7 @@ impl AppState { let key = match event { Event::Key(key) if key.kind != KeyEventKind::Release => { if key.code != KeyCode::Char('\r') { - self.traversal_state.received_event = true; + self.received_event = true; } key } diff --git a/src/interactive/app/terminal_app.rs b/src/interactive/app/terminal_app.rs index 537cb6f..5d57cf1 100644 --- a/src/interactive/app/terminal_app.rs +++ b/src/interactive/app/terminal_app.rs @@ -13,7 +13,7 @@ use tui_react::Terminal; use crate::{crossdev, interactive::widgets::MainWindow}; use super::{ - app_state::{AppState, ProcessingResult, TraversalState}, + app_state::{AppState, ProcessingResult}, sorted_entries, DisplayOptions, }; @@ -26,14 +26,6 @@ pub struct TerminalApp { pub walk_options: WalkOptions, } -pub type TraversalEntry = - Result<jwalk::DirEntry<((), Option<Result<std::fs::Metadata, jwalk::Error>>)>, jwalk::Error>; - -pub enum TraversalEvent { - Entry(TraversalEntry, Arc<PathBuf>, u64), - Finished(u64), -} - impl TerminalApp { pub fn initialize<B>( terminal: &mut Terminal<B>, @@ -87,59 +79,16 @@ impl TerminalApp { Ok(app) } - pub fn scan(&mut self, input: Vec<PathBuf>) -> Result<Receiver<TraversalEvent>> { - self.state.traversal_state = TraversalState::new(self.traversal.root_index); - self.state.is_scanning = true; - - let (entry_tx, entry_rx) = crossbeam::channel::bounded(100); - std::thread::Builder::new() - .name("dua-fs-walk-dispatcher".to_string()) - .spawn({ - let walk_options = self.walk_options.clone(); - let mut io_errors: u64 = 0; - move || { - for root_path in input.into_iter() { - let device_id = match crossdev::init(root_path.as_ref()) { - Ok(id) => id, - Err(_) => { - io_errors += 1; - continue; - } - }; - - let root_path = Arc::new(root_path); - for entry in walk_options - .iter_from_path(root_path.as_ref(), device_id) - .into_iter() - { - if entry_tx - .send(TraversalEvent::Entry( - entry, - Arc::clone(&root_path), - device_id, - )) - .is_err() - { - // The channel is closed, this means the user has - // requested to quit the app. Abort the walking. - return; - } - } - } - if entry_tx.send(TraversalEvent::Finished(io_errors)).is_err() { - log::error!("Failed to send TraversalEvents::Finished event"); - } - } - })?; - - Ok(entry_rx) + pub fn traverse(&mut self, input: Vec<PathBuf>) -> Result<()> { + self.state + .traverse(&self.traversal, &self.walk_options, input)?; + Ok(()) } pub fn process_events<B>( &mut self, terminal: &mut Terminal<B>, events: Receiver<Event>, - traversal: Receiver<TraversalEvent>, ) -> Result<WalkResult> where B: Backend, @@ -149,9 +98,7 @@ impl TerminalApp { &mut self.traversal, &mut self.display, terminal, - &self.walk_options, events, - traversal, )? { ProcessingResult::ExitRequested(res) => Ok(res), } diff --git a/src/interactive/app/tests/utils.rs b/src/interactive/app/tests/utils.rs index 6be28ab..133dd9a 100644 --- a/src/interactive/app/tests/utils.rs +++ b/src/interactive/app/tests/utils.rs @@ -19,10 +19,7 @@ use std::{ use tui::backend::TestBackend; use tui_react::Terminal; -use crate::interactive::{ - app::tests::FIXTURE_PATH, - terminal_app::{TerminalApp, TraversalEvent}, -}; +use crate::interactive::{app::tests::FIXTURE_PATH, terminal_app::TerminalApp}; pub fn into_keys<'a>( codes: impl IntoIterator<Item = KeyCode> + 'a, @@ -191,7 +188,7 @@ pub fn initialized_app_and_terminal_with_closure( let mut app = TerminalApp::initialize(&mut terminal, walk_options, ByteFormat::Metric)?; let input_paths = fixture_paths.iter().map(|c| convert(c.as_ref())).collect(); - let traversal_rx = app.scan(input_paths)?; + let traversal_rx = app.traverse(input_paths)?; Ok((terminal, app, traversal_rx)) } |