From 7e4629b7065ea7ad0f917f26b7c326f4159301f1 Mon Sep 17 00:00:00 2001 From: luxean Date: Tue, 17 Feb 2026 12:27:23 +0100 Subject: [PATCH 1/3] perf: remove name from EntryNode --- src/backend/entry_node.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/backend/entry_node.rs b/src/backend/entry_node.rs index f62d249..4cdd92d 100644 --- a/src/backend/entry_node.rs +++ b/src/backend/entry_node.rs @@ -17,7 +17,6 @@ pub enum EntryType { #[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, @@ -65,7 +64,7 @@ impl EntryNodeView { 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, @@ -93,12 +92,10 @@ impl EntryNode { 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), @@ -155,7 +152,12 @@ 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) + write!( + f, + "{:<20} • {}", + extract_file_name(&self.path), + self.sizes.apparent_size + ) } } @@ -166,7 +168,6 @@ impl TryFrom<&jwalk::DirEntry> for EntryNode { 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); @@ -176,7 +177,6 @@ impl TryFrom<&jwalk::DirEntry> for EntryNode { }; Ok(EntryNode { - name, path: value.path().clone(), sizes: size, dir_size, From 773bdc31e5ce25b43e40beca1d49eb3f92c830ee Mon Sep 17 00:00:00 2001 From: luxean Date: Thu, 19 Feb 2026 16:05:05 +0100 Subject: [PATCH 2/3] perf: remove metadata from EntryNode --- src/backend/disko_tree.rs | 79 ++++++++++++--------- src/backend/entry_node.rs | 141 +++++++++++++++++--------------------- src/ui/renderer.rs | 7 +- 3 files changed, 110 insertions(+), 117 deletions(-) diff --git a/src/backend/disko_tree.rs b/src/backend/disko_tree.rs index 72120c4..447e80c 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 }) @@ -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 @@ -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.sizes); // 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.sizes = EntrySize::default(); + } + size += entry.sizes; + 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); } diff --git a/src/backend/entry_node.rs b/src/backend/entry_node.rs index 4cdd92d..debedbd 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,30 +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) path: PathBuf, pub(crate) sizes: EntrySize, - pub(crate) dir_size: Option, 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 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, + size: EntrySize, + access_time: Option>, +} + +impl EntryDetails { + pub fn new(metadata: &Metadata, path: &Path) -> Self { + Self { + mode: extract_mode(metadata), + size: EntrySize::new(path, metadata), + access_time: metadata.accessed().ok().map(DateTime::::from), + } + } +} + +#[derive(Clone, Copy)] pub enum Mode { Permissions(u32), Attributes(u32), @@ -49,16 +71,33 @@ impl EntryNodeView { name: extract_file_name(&path), path, sizes: EntrySize::default(), - dir_size: Some(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.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 { @@ -67,15 +106,9 @@ impl From<&EntryNode> for EntryNodeView { name: extract_file_name(&entry_node.path), path: entry_node.path.clone(), sizes: entry_node.sizes, - dir_size: entry_node.dir_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, } } @@ -84,27 +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; + pub(crate) fn new(path: PathBuf, metadata: &Metadata) -> Self { + let sizes = EntrySize::new(&path, metadata); + Self { + path, + sizes, + descendants_count: 0, + entry_type: metadata.into(), } - - let size = EntrySize::new(path, &metadata); - - Some(( - Self { - path: path.to_path_buf(), - sizes: EntrySize::default(), - dir_size: Some(size), - descendants_count: 0, - entry_type: EntryType::Directory, - metadata, - }, - size, - )) } pub(crate) fn delete_entry(&self) -> std::io::Result<()> { @@ -139,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 } @@ -160,40 +180,3 @@ impl Display for EntryNode { ) } } - -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 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 { - 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 - } -} diff --git a/src/ui/renderer.rs b/src/ui/renderer.rs index 02cc5c3..50a6dc3 100644 --- a/src/ui/renderer.rs +++ b/src/ui/renderer.rs @@ -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,8 +409,7 @@ 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 = parent.sizes - parent.self_size(); Row::new(vec![ self.get_selection_cell(is_selected), From ae55a9c5095c2b40a1760b3730fe9c58371e2ab9 Mon Sep 17 00:00:00 2001 From: luxean Date: Thu, 19 Feb 2026 16:20:31 +0100 Subject: [PATCH 3/3] refactor: rename size names to be clearer --- src/backend/disko_tree.rs | 16 ++++++++-------- src/backend/entry_node.rs | 20 ++++++++++---------- src/backend/entry_size.rs | 24 ++++++++++++------------ src/ui/renderer.rs | 26 +++++++++++++------------- 4 files changed, 43 insertions(+), 43 deletions(-) diff --git a/src/backend/disko_tree.rs b/src/backend/disko_tree.rs index 447e80c..874e0fb 100644 --- a/src/backend/disko_tree.rs +++ b/src/backend/disko_tree.rs @@ -78,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) } }); @@ -286,7 +286,7 @@ impl DiskoTree { child_data.delete_entry()?; - deleted_size += child_data.sizes; + deleted_size += child_data.size; self.tree .clone() .write() @@ -326,7 +326,7 @@ impl DiskoTree { // yank the size leaving 0, since we will add the size later // during backpropagation - let mut size = mem::take(&mut dir_node.sizes); + let mut size = mem::take(&mut dir_node.size); // Create node on tree. let node = Self::attach_to_tree(state, dir_node); @@ -347,9 +347,9 @@ impl DiskoTree { let mut entry = EntryNode::new(child.path(), &metadata); if state.file_has_been_seen(&metadata) { - entry.sizes = EntrySize::default(); + entry.size = EntrySize::default(); } - size += entry.sizes; + size += entry.size; Tree::attach_child(&node, entry); } @@ -384,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 debedbd..f3467db 100644 --- a/src/backend/entry_node.rs +++ b/src/backend/entry_node.rs @@ -28,7 +28,7 @@ impl From<&Metadata> for EntryType { #[allow(dead_code)] pub(crate) struct EntryNode { pub(crate) path: PathBuf, - pub(crate) sizes: EntrySize, + pub(crate) size: EntrySize, pub(crate) descendants_count: usize, pub(crate) entry_type: EntryType, } @@ -36,7 +36,7 @@ pub(crate) struct EntryNode { pub struct EntryNodeView { pub name: String, pub path: PathBuf, - pub sizes: EntrySize, + pub size: EntrySize, pub descendants_count: usize, pub entry_type: EntryType, details: OnceCell>, @@ -45,7 +45,7 @@ pub struct EntryNodeView { pub struct EntryDetails { mode: Option, - size: EntrySize, + self_size: EntrySize, access_time: Option>, } @@ -53,7 +53,7 @@ impl EntryDetails { pub fn new(metadata: &Metadata, path: &Path) -> Self { Self { mode: extract_mode(metadata), - size: EntrySize::new(path, metadata), + self_size: EntrySize::new(path, metadata), access_time: metadata.accessed().ok().map(DateTime::::from), } } @@ -70,7 +70,7 @@ impl EntryNodeView { Self { name: extract_file_name(&path), path, - sizes: EntrySize::default(), + size: EntrySize::default(), descendants_count: 0, entry_type: EntryType::Directory, details: OnceCell::new(), @@ -88,7 +88,7 @@ impl EntryNodeView { } pub(crate) fn self_size(&self) -> EntrySize { - self.details().map(|d| d.size).unwrap_or_default() + self.details().map(|d| d.self_size).unwrap_or_default() } pub(crate) fn access_time(&self) -> Option> { @@ -105,7 +105,7 @@ impl From<&EntryNode> for EntryNodeView { Self { name: extract_file_name(&entry_node.path), path: entry_node.path.clone(), - sizes: entry_node.sizes, + size: entry_node.size, descendants_count: entry_node.descendants_count, entry_type: entry_node.entry_type, details: OnceCell::new(), @@ -118,10 +118,10 @@ impl From<&EntryNode> for EntryNodeView { impl EntryNode { pub(crate) fn new(path: PathBuf, metadata: &Metadata) -> Self { - let sizes = EntrySize::new(&path, metadata); + let size = EntrySize::new(&path, metadata); Self { path, - sizes, + size, descendants_count: 0, entry_type: metadata.into(), } @@ -176,7 +176,7 @@ impl Display for EntryNode { f, "{:<20} • {}", extract_file_name(&self.path), - self.sizes.apparent_size + 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 50a6dc3..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 = @@ -409,20 +409,20 @@ impl Renderer { let is_focused = table_state.is_focused(index); let is_selected = table_state.is_selected(index); - let total_size = parent.sizes - parent.self_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)) }); @@ -489,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); @@ -542,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 = @@ -666,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::(); @@ -676,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!(