diff --git a/src/backend/disko_tree.rs b/src/backend/disko_tree.rs index 72120c4..874e0fb 100644 --- a/src/backend/disko_tree.rs +++ b/src/backend/disko_tree.rs @@ -1,5 +1,5 @@ use std::{ - fmt, + fmt, fs, mem, path::{Path, PathBuf}, sync::{ atomic::{AtomicBool, Ordering}, @@ -60,10 +60,7 @@ impl DiskoTree { self.root.clone() } - fn get_children( - node: &std::sync::RwLockReadGuard<'_, Node>, - sort_by_disk_size: bool, - ) -> Vec { + fn get_children(node: &Node, sort_by_disk_size: bool) -> Vec { let mut children: Vec = node .get_children() .iter() @@ -73,6 +70,7 @@ impl DiskoTree { .read() .expect("Failed to read child while getting children"); let mut entry = EntryNodeView::from(&child.data); + entry.index_to_original_node = Some(index); entry }) @@ -80,9 +78,9 @@ impl DiskoTree { children.sort_by(|a, b| { if sort_by_disk_size { - b.sizes.disk_size.cmp(&a.sizes.disk_size) + b.size.disk.cmp(&a.size.disk) } else { - b.sizes.apparent_size.cmp(&a.sizes.apparent_size) + b.size.apparent.cmp(&a.size.apparent) } }); @@ -128,6 +126,15 @@ impl DiskoTree { Ok(()) } + pub(crate) fn get_view_of_directory( + directory: &Node, + sort_by_disk_size: bool, + ) -> (EntryNodeView, Vec) { + let children = Self::get_children(directory, sort_by_disk_size); + let directory_view = EntryNodeView::from(&directory.data); + (directory_view, children) + } + /// Get the view of the current directory and its children. /// Returns `None` if the current directory is not set, i.e., the traversal /// has not yet computed a root. @@ -147,9 +154,11 @@ impl DiskoTree { .as_ref()? .read() .expect("Failed to read current directory"); - let children = Self::get_children(¤t_directory, sort_by_disk_size); - let current_directory_view = EntryNodeView::from(¤t_directory.data); - Some((current_directory_view, children)) + + Some(Self::get_view_of_directory( + ¤t_directory, + sort_by_disk_size, + )) } /// Get the view of the subdirectory of the current directory at the given @@ -277,7 +286,7 @@ impl DiskoTree { child_data.delete_entry()?; - deleted_size += child_data.sizes; + deleted_size += child_data.size; self.tree .clone() .write() @@ -308,42 +317,44 @@ impl DiskoTree { if depth.is_none() { return; } - // Create entry node from jwalks - let Some((dir_node, dir_size)) = EntryNode::new_dir(dir_path) else { + + let Ok(dir_metadata) = fs::metadata(dir_path) else { return; }; - // Count size of file children. - let mut size = dir_size; + let mut dir_node = EntryNode::new(dir_path.to_path_buf(), &dir_metadata); + + // yank the size leaving 0, since we will add the size later + // during backpropagation + let mut size = mem::take(&mut dir_node.size); // Create node on tree. let node = Self::attach_to_tree(state, dir_node); - children - .iter_mut() - // Put reference to results inner types. - .map(|dir_entry_result| dir_entry_result.as_ref()) - // Filter errors out and return just `DirEntry` entries. - .filter_map(std::result::Result::ok) - .filter(|dir_entry| dir_entry.file_type.is_file()) - // Map to our `EntryNode`s. - .map(EntryNode::try_from) - // Throw away when convertion failed. - .filter_map(Result::ok) - // Finaly process the file children. - .for_each(|mut child_node| { - if state.file_has_been_seen(&child_node.metadata) { - child_node.sizes = EntrySize::default(); - } - size += child_node.sizes; - Tree::attach_child(&node, child_node); - }); + for child in children { + let Ok(child) = child else { + continue; + }; + + if !child.file_type().is_file() { + continue; + } + + let Ok(metadata) = child.metadata() else { + continue; + }; + + let mut entry = EntryNode::new(child.path(), &metadata); + + if state.file_has_been_seen(&metadata) { + entry.size = EntrySize::default(); + } + size += entry.size; + Tree::attach_child(&node, entry); + } - // Propagate size up including this node to root (including). Self::backprop_size(&node, size, BackpropOperation::Add); - // Move (i.e. not .clone()) reference to this node as a parent - // for the next iteration. state.ancestor = TreeWalkAncestor::Parent(node); } @@ -373,8 +384,8 @@ impl DiskoTree { .expect("Failed to write while backpropagating size"); match operation { - BackpropOperation::Add => node.data.sizes += size, - BackpropOperation::Subtract => node.data.sizes -= size, + BackpropOperation::Add => node.data.size += size, + BackpropOperation::Subtract => node.data.size -= size, }; }); } diff --git a/src/backend/entry_node.rs b/src/backend/entry_node.rs index f62d249..f3467db 100644 --- a/src/backend/entry_node.rs +++ b/src/backend/entry_node.rs @@ -1,4 +1,5 @@ use std::{ + cell::OnceCell, fmt::Display, fs::{self, Metadata}, path::{Path, PathBuf}, @@ -6,7 +7,7 @@ use std::{ use chrono::{DateTime, Local}; -use super::{entry_size::EntrySize, tree_walk_state::CustomJWalkClientState}; +use super::entry_size::EntrySize; #[derive(Clone, Copy, Debug)] pub enum EntryType { @@ -14,31 +15,51 @@ pub enum EntryType { File, } +impl From<&Metadata> for EntryType { + fn from(metadata: &Metadata) -> Self { + if metadata.is_dir() { + return EntryType::Directory; + } + EntryType::File + } +} + #[derive(Clone, Debug)] #[allow(dead_code)] pub(crate) struct EntryNode { - pub(crate) name: String, pub(crate) path: PathBuf, - pub(crate) sizes: EntrySize, - pub(crate) dir_size: Option, + pub(crate) size: EntrySize, pub(crate) descendants_count: usize, pub(crate) entry_type: EntryType, - pub(crate) metadata: fs::Metadata, } pub struct EntryNodeView { pub name: String, pub path: PathBuf, - pub sizes: EntrySize, - pub dir_size: Option, + pub size: EntrySize, pub descendants_count: usize, pub entry_type: EntryType, - pub mode: Option, - pub access_time: Option>, + details: OnceCell>, pub index_to_original_node: Option, } -#[derive(Clone, Copy, Debug)] +pub struct EntryDetails { + mode: Option, + self_size: EntrySize, + access_time: Option>, +} + +impl EntryDetails { + pub fn new(metadata: &Metadata, path: &Path) -> Self { + Self { + mode: extract_mode(metadata), + self_size: EntrySize::new(path, metadata), + access_time: metadata.accessed().ok().map(DateTime::::from), + } + } +} + +#[derive(Clone, Copy)] pub enum Mode { Permissions(u32), Attributes(u32), @@ -49,34 +70,45 @@ impl EntryNodeView { Self { name: extract_file_name(&path), path, - sizes: EntrySize::default(), - dir_size: Some(EntrySize::default()), + size: EntrySize::default(), descendants_count: 0, entry_type: EntryType::Directory, - // Unknown here for now, this needs to be updated later during the - // backend refactor. - mode: None, - access_time: None, + details: OnceCell::new(), index_to_original_node: None, } } + + fn details(&self) -> Option<&EntryDetails> { + self.details + .get_or_init(|| { + let metadata = &fs::metadata(&self.path).ok()?; + Some(EntryDetails::new(metadata, &self.path)) + }) + .as_ref() + } + + pub(crate) fn self_size(&self) -> EntrySize { + self.details().map(|d| d.self_size).unwrap_or_default() + } + + pub(crate) fn access_time(&self) -> Option> { + self.details().and_then(|d| d.access_time) + } + + pub(crate) fn mode(&self) -> Option { + self.details().and_then(|d| d.mode) + } } impl From<&EntryNode> for EntryNodeView { fn from(entry_node: &EntryNode) -> Self { Self { - name: entry_node.name.clone(), + name: extract_file_name(&entry_node.path), path: entry_node.path.clone(), - sizes: entry_node.sizes, - dir_size: entry_node.dir_size, + size: entry_node.size, descendants_count: entry_node.descendants_count, entry_type: entry_node.entry_type, - access_time: entry_node - .metadata - .accessed() - .ok() - .map(DateTime::::from), - mode: extract_mode(&entry_node.metadata), + details: OnceCell::new(), index_to_original_node: None, } } @@ -85,29 +117,14 @@ impl From<&EntryNode> for EntryNodeView { // Convenience helpers impl EntryNode { - pub(crate) fn new_dir(path: &Path) -> Option<(Self, EntrySize)> { - let Ok(metadata) = fs::metadata(path) else { - return None; - }; - if !metadata.is_dir() { - return None; - } - - let name = extract_file_name(path); - let size = EntrySize::new(path, &metadata); - - Some(( - Self { - name, - path: path.to_path_buf(), - sizes: EntrySize::default(), - dir_size: Some(size), - descendants_count: 0, - entry_type: EntryType::Directory, - metadata, - }, + pub(crate) fn new(path: PathBuf, metadata: &Metadata) -> Self { + let size = EntrySize::new(&path, metadata); + Self { + path, size, - )) + descendants_count: 0, + entry_type: metadata.into(), + } } pub(crate) fn delete_entry(&self) -> std::io::Result<()> { @@ -142,12 +159,12 @@ impl From<&Metadata> for Mode { } #[cfg(any(unix, windows))] -pub fn extract_mode(metadata: &Metadata) -> Option { - Some(Mode::from(metadata)) +fn extract_mode(metadata: &Metadata) -> Option { + Some(metadata.into()) } #[cfg(not(any(unix, windows)))] -pub fn extract_mode(_metadata: &Metadata) -> Option { +fn extract_mode(_metadata: &Metadata) -> Option { None } @@ -155,45 +172,11 @@ pub fn extract_mode(_metadata: &Metadata) -> Option { impl Display for EntryNode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:<20} • {}", self.name, self.sizes.apparent_size) - } -} - -impl TryFrom<&jwalk::DirEntry> for EntryNode { - type Error = &'static str; - - fn try_from(value: &jwalk::DirEntry) -> Result { - let Ok(metadata) = value.metadata() else { - return Err("Error getting metadata from DirEntry"); - }; - let name = value.file_name().to_string_lossy().to_string(); - let entry_type = Self::extract_entry_type(value); - - let size = EntrySize::new(value.path().as_path(), &metadata); - let dir_size = match entry_type { - EntryType::Directory => Some(size), - EntryType::File => None, - }; - - Ok(EntryNode { - name, - path: value.path().clone(), - sizes: size, - dir_size, - descendants_count: 0, - entry_type, - metadata, - }) - } -} - -// Helper functions for TryFrom - -impl EntryNode { - fn extract_entry_type(dir_entry: &jwalk::DirEntry) -> EntryType { - if dir_entry.file_type.is_dir() { - return EntryType::Directory; - } - EntryType::File + write!( + f, + "{:<20} • {}", + extract_file_name(&self.path), + self.size.apparent + ) } } diff --git a/src/backend/entry_size.rs b/src/backend/entry_size.rs index a5dd9c2..2e2eb09 100644 --- a/src/backend/entry_size.rs +++ b/src/backend/entry_size.rs @@ -4,15 +4,15 @@ use std::path::Path; #[derive(Clone, Copy, Debug, Default)] pub struct EntrySize { - pub apparent_size: u64, - pub disk_size: u64, + pub apparent: u64, + pub disk: u64, } impl EntrySize { pub fn new(path: &Path, metadata: &std::fs::Metadata) -> Self { Self { - apparent_size: metadata.len(), - disk_size: path.size_on_disk_fast(metadata).unwrap_or(0), + apparent: metadata.len(), + disk: path.size_on_disk_fast(metadata).unwrap_or(0), } } } @@ -22,8 +22,8 @@ impl Add for EntrySize { fn add(self, other: Self) -> Self::Output { Self { - apparent_size: self.apparent_size + other.apparent_size, - disk_size: self.disk_size + other.disk_size, + apparent: self.apparent + other.apparent, + disk: self.disk + other.disk, } } } @@ -33,8 +33,8 @@ impl Sub for EntrySize { fn sub(self, other: Self) -> Self::Output { Self { - apparent_size: self.apparent_size - other.apparent_size, - disk_size: self.disk_size - other.disk_size, + apparent: self.apparent - other.apparent, + disk: self.disk - other.disk, } } } @@ -42,8 +42,8 @@ impl Sub for EntrySize { impl AddAssign for EntrySize { fn add_assign(&mut self, rhs: Self) { *self = EntrySize { - apparent_size: self.apparent_size + rhs.apparent_size, - disk_size: self.disk_size + rhs.disk_size, + apparent: self.apparent + rhs.apparent, + disk: self.disk + rhs.disk, }; } } @@ -51,8 +51,8 @@ impl AddAssign for EntrySize { impl SubAssign for EntrySize { fn sub_assign(&mut self, rhs: Self) { *self = EntrySize { - apparent_size: self.apparent_size - rhs.apparent_size, - disk_size: self.disk_size - rhs.disk_size, + apparent: self.apparent - rhs.apparent, + disk: self.disk - rhs.disk, }; } } diff --git a/src/ui/renderer.rs b/src/ui/renderer.rs index 02cc5c3..f6312c1 100644 --- a/src/ui/renderer.rs +++ b/src/ui/renderer.rs @@ -293,9 +293,9 @@ impl Renderer { frame.render_widget(block, area); let root_size = Byte::from_u64(if state.show_disk_size { - state.current_directory.sizes.disk_size + state.current_directory.size.disk } else { - state.current_directory.sizes.apparent_size + state.current_directory.size.apparent }) .get_appropriate_unit(byte_unit::UnitType::Decimal); let root_size = @@ -328,12 +328,12 @@ impl Renderer { let mode_area = left_half_chunks[0]; - if let Some(Mode::Attributes(value) | Mode::Permissions(value)) = focused.mode { + if let Some(Mode::Attributes(value) | Mode::Permissions(value)) = focused.mode() { let mode = self.get_mode(value); frame.render_widget(mode, mode_area); } - let Some(accessed_time) = focused.access_time else { + let Some(accessed_time) = focused.access_time() else { return; }; let access_time = Paragraph::new(accessed_time.format("%d.%m.%Y %H:%M").to_string()) @@ -409,21 +409,20 @@ impl Renderer { let is_focused = table_state.is_focused(index); let is_selected = table_state.is_selected(index); - let dir_size = parent.dir_size.unwrap_or_default(); - let total_size = parent.sizes - dir_size; + let total_size_excluding_self = parent.size - parent.self_size(); Row::new(vec![ self.get_selection_cell(is_selected), self.get_name_cell(data.name.clone(), data.entry_type, is_focused, app_focus), self.get_size_progress_cell( - data.sizes, - total_size, + data.size, + total_size_excluding_self, show_bar, show_disk_size, is_focused, app_focus, ), - self.get_size_cell(data.sizes, show_disk_size, is_focused, app_focus), + self.get_size_cell(data.size, show_disk_size, is_focused, app_focus), ]) .style(self.get_row_style(is_focused, app_focus)) }); @@ -490,9 +489,9 @@ impl Renderer { app_focus: &AppFocus, ) -> Cell<'a> { let (size, total_size) = if show_disk_size { - (size.disk_size, total_size.disk_size) + (size.disk, total_size.disk) } else { - (size.apparent_size, total_size.apparent_size) + (size.apparent, total_size.apparent) }; let rate = (size as f64 / total_size as f64).min(1.0); @@ -543,9 +542,9 @@ impl Renderer { }; let size = if show_disk_size { - size.disk_size + size.disk } else { - size.apparent_size + size.apparent }; let appropriate_size = @@ -667,9 +666,9 @@ impl Renderer { .iter() .map(|e| { if state.show_disk_size { - e.sizes.disk_size + e.size.disk } else { - e.sizes.apparent_size + e.size.apparent } }) .sum::(); @@ -677,7 +676,7 @@ impl Renderer { let text = { if selected.is_empty() { if let Some(focused) = table.focused() { - let size = Byte::from_u64(focused.sizes.apparent_size) + let size = Byte::from_u64(focused.size.apparent) .get_appropriate_unit(byte_unit::UnitType::Decimal); match focused.entry_type { EntryType::Directory => format!(