diff options
author | Sebastian Thiel <sthiel@thoughtworks.com> | 2019-06-03 07:31:31 +0530 |
---|---|---|
committer | Sebastian Thiel <sthiel@thoughtworks.com> | 2019-06-03 07:31:31 +0530 |
commit | 2ce606f607fa967f94d49c5413c4b347e628e88e (patch) | |
tree | 04212a76420c2cc93948f311bc9999364903270e /src/interactive | |
parent | 24097bd19ee53ca7a4a635e6ea63c3e3c63bdc2b (diff) |
move modules into their own files
Diffstat (limited to 'src/interactive')
-rw-r--r-- | src/interactive/app.rs | 197 | ||||
-rw-r--r-- | src/interactive/mod.rs | 4 | ||||
-rw-r--r-- | src/interactive/widgets.rs | 29 |
3 files changed, 230 insertions, 0 deletions
diff --git a/src/interactive/app.rs b/src/interactive/app.rs new file mode 100644 index 0000000..7021045 --- /dev/null +++ b/src/interactive/app.rs @@ -0,0 +1,197 @@ +use crate::{WalkOptions, WalkResult}; +use failure::Error; +use petgraph::{prelude::NodeIndex, Directed, Direction, Graph}; +use std::time::{Duration, Instant}; +use std::{ffi::OsString, io, path::PathBuf}; +use termion::input::{Keys, TermReadEventsAndRaw}; +use tui::widgets::Widget; +use tui::{backend::Backend, Terminal}; + +pub type TreeIndexType = u32; +pub type TreeIndex = NodeIndex<TreeIndexType>; +pub type Tree = Graph<EntryData, (), Directed, TreeIndexType>; + +#[derive(Eq, PartialEq, Debug, Default)] +pub struct EntryData { + pub name: OsString, + /// The entry's size in bytes. If it's a directory, the size is the aggregated file size of all children + pub size: u64, + /// If set, the item meta-data could not be obtained + pub metadata_io_error: bool, +} + +/// State and methods representing the interactive disk usage analyser for the terminal +#[derive(Default, Debug)] +pub struct TerminalApp { + /// A tree representing the entire filestem traversal + pub tree: Tree, + /// The top-level node of the tree. + pub root_index: TreeIndex, + /// Amount of files or directories we have seen during the filesystem traversal + pub entries_traversed: u64, + /// Total amount of IO errors encountered when traversing the filesystem + pub io_errors: u64, +} + +const GUI_REFRESH_RATE: Duration = Duration::from_millis(100); + +impl TerminalApp { + pub fn process_events<B, R>( + &mut self, + _terminal: &mut Terminal<B>, + _keys: Keys<R>, + ) -> Result<WalkResult, Error> + where + B: Backend, + R: io::Read + TermReadEventsAndRaw, + { + unimplemented!() + } + + pub fn initialize<B>( + terminal: &mut Terminal<B>, + options: WalkOptions, + input: Vec<PathBuf>, + ) -> Result<TerminalApp, Error> + where + B: Backend, + { + fn set_size_or_panic( + tree: &mut Tree, + parent_node_idx: TreeIndex, + current_size_at_depth: u64, + ) { + tree.node_weight_mut(parent_node_idx) + .expect("node for parent index we just retrieved") + .size = current_size_at_depth; + } + 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") + } + fn pop_or_panic(v: &mut Vec<u64>) -> u64 { + v.pop().expect("sizes per level to be in sync with graph") + } + let mut tree = Tree::new(); + let mut io_errors = 0u64; + let mut entries_traversed = 0u64; + + let root_index = tree.add_node(EntryData::default()); + let (mut previous_node_idx, mut parent_node_idx) = (root_index, root_index); + let mut sizes_per_depth_level = Vec::new(); + let mut current_size_at_depth = 0; + let mut previous_depth = 0; + + let mut last_checked = Instant::now(); + + const INITIAL_CHECK_INTERVAL: usize = 500; + let mut check_instant_every = INITIAL_CHECK_INTERVAL; + let mut last_seen_eid; + + for path in input.into_iter() { + last_seen_eid = 0; + for (eid, entry) in options + .iter_from_path(path.as_ref()) + .into_iter() + .enumerate() + { + entries_traversed += 1; + let mut data = EntryData::default(); + match entry { + Ok(entry) => { + data.name = entry.file_name; + let file_size = match entry.metadata { + Some(Ok(ref m)) if !m.is_dir() => m.len(), + Some(Ok(_)) => 0, + Some(Err(_)) => { + io_errors += 1; + data.metadata_io_error = true; + 0 + } + None => unreachable!( + "we ask for metadata, so we at least have Some(Err(..))). Issue in jwalk?" + ), + }; + + match (entry.depth, previous_depth) { + (n, p) if n > p => { + sizes_per_depth_level.push(current_size_at_depth); + current_size_at_depth = file_size; + parent_node_idx = previous_node_idx; + } + (n, p) if n < p => { + for _ in n..p { + set_size_or_panic( + &mut tree, + parent_node_idx, + current_size_at_depth, + ); + current_size_at_depth += + pop_or_panic(&mut sizes_per_depth_level); + parent_node_idx = parent_or_panic(&mut tree, parent_node_idx); + } + current_size_at_depth += file_size; + set_size_or_panic( + &mut tree, + parent_node_idx, + current_size_at_depth, + ); + } + _ => { + current_size_at_depth += file_size; + } + }; + + data.size = file_size; + let entry_index = tree.add_node(data); + + tree.add_edge(parent_node_idx, entry_index, ()); + previous_node_idx = entry_index; + previous_depth = entry.depth; + } + Err(_) => io_errors += 1, + } + + if eid != 0 + && eid % check_instant_every == 0 + && last_checked.elapsed() >= GUI_REFRESH_RATE + { + let now = Instant::now(); + let elapsed = (now - last_checked).as_millis() as f64; + check_instant_every = (INITIAL_CHECK_INTERVAL as f64 + * ((eid - last_seen_eid) as f64 / INITIAL_CHECK_INTERVAL as f64) + * (GUI_REFRESH_RATE.as_millis() as f64 / elapsed)) + as usize; + last_seen_eid = eid; + last_checked = now; + + terminal.draw(|mut f| { + let full_screen = f.size(); + super::widgets::Entries { + tree: &tree, + root: root_index, + } + .render(&mut f, full_screen) + })?; + } + } + } + + sizes_per_depth_level.push(current_size_at_depth); + current_size_at_depth = 0; + for _ in 0..previous_depth { + current_size_at_depth += pop_or_panic(&mut sizes_per_depth_level); + set_size_or_panic(&mut tree, parent_node_idx, current_size_at_depth); + parent_node_idx = parent_or_panic(&mut tree, parent_node_idx); + } + set_size_or_panic(&mut tree, root_index, current_size_at_depth); + + Ok(TerminalApp { + tree, + root_index, + entries_traversed, + io_errors, + }) + } +} diff --git a/src/interactive/mod.rs b/src/interactive/mod.rs new file mode 100644 index 0000000..2268008 --- /dev/null +++ b/src/interactive/mod.rs @@ -0,0 +1,4 @@ +mod app; +mod widgets; + +pub use self::app::*; diff --git a/src/interactive/widgets.rs b/src/interactive/widgets.rs new file mode 100644 index 0000000..61095ba --- /dev/null +++ b/src/interactive/widgets.rs @@ -0,0 +1,29 @@ +use super::{Tree, TreeIndex}; +use petgraph::Direction; +use tui::buffer::Buffer; +use tui::layout::{Corner, Rect}; +use tui::widgets::{Block, Borders, List, Text, Widget}; + +pub struct Entries<'a> { + pub tree: &'a Tree, + pub root: TreeIndex, +} + +impl<'a> Widget for Entries<'a> { + fn draw(&mut self, area: Rect, buf: &mut Buffer) { + let Self { tree, root } = self; + List::new( + tree.neighbors_directed(*root, Direction::Outgoing) + .filter_map(|w| { + tree.node_weight(w).map(|w| { + Text::Raw( + format!("{} | ----- | {}", w.size, w.name.to_string_lossy()).into(), + ) + }) + }), + ) + .block(Block::default().borders(Borders::ALL).title("Entries")) + .start_corner(Corner::TopLeft) + .draw(area, buf); + } +} |