From 2f08c05afe62f6ab8a63b513b7ec3420e79a4e6d Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 15 Nov 2024 17:30:25 -0700 Subject: [PATCH] Refactor directory size to allow for cancellation --- src/mounter/gvfs.rs | 5 +- src/operation/mod.rs | 23 ++++++- src/tab.rs | 159 +++++++++++++++++++++++++++---------------- 3 files changed, 124 insertions(+), 63 deletions(-) diff --git a/src/mounter/gvfs.rs b/src/mounter/gvfs.rs index a58b6db..e5b046c 100644 --- a/src/mounter/gvfs.rs +++ b/src/mounter/gvfs.rs @@ -10,7 +10,7 @@ use super::{Mounter, MounterAuth, MounterItem, MounterItems, MounterMessage}; use crate::{ config::IconSizes, err_str, - tab::{self, ItemMetadata, ItemThumbnail, Location}, + tab::{self, DirSize, ItemMetadata, ItemThumbnail, Location}, }; fn gio_icon_to_path(icon: &gio::Icon, size: u16) -> Option { @@ -135,7 +135,8 @@ fn network_scan(uri: &str, sizes: IconSizes) -> Result, String> { selected: false, highlighted: false, overlaps_drag_rect: false, - size: None, + //TODO: scan directory size on gvfs mounts? + dir_size: DirSize::NotDirectory, }); } Ok(items) diff --git a/src/operation/mod.rs b/src/operation/mod.rs index 2431c27..108c2ec 100644 --- a/src/operation/mod.rs +++ b/src/operation/mod.rs @@ -231,19 +231,22 @@ pub enum ControllerState { Running, } +#[derive(Debug)] struct ControllerInner { state: Mutex, condvar: Condvar, } -#[derive(Clone)] +#[derive(Debug)] pub struct Controller { + primary: bool, inner: Arc, } impl Controller { pub fn new() -> Self { Self { + primary: true, inner: Arc::new(ControllerInner { state: Mutex::new(ControllerState::Running), condvar: Condvar::new(), @@ -295,6 +298,24 @@ impl Controller { } } +impl Clone for Controller { + fn clone(&self) -> Self { + Self { + primary: false, + inner: self.inner.clone(), + } + } +} + +impl Drop for Controller { + fn drop(&mut self) { + // Cancel operations if primary controller is dropped + if self.primary { + self.cancel(); + } + } +} + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum ReplaceResult { Replace(bool), diff --git a/src/tab.rs b/src/tab.rs index 8b588d2..cedb092 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -69,6 +69,7 @@ use crate::{ mime_icon::{mime_for_path, mime_icon}, mounter::MOUNTERS, mouse_area, + operation::Controller, thumbnailer::thumbnailer, }; use unix_permissions_ext::UNIXPermissionsExt; @@ -473,6 +474,12 @@ pub fn item_from_entry( 0 }; + let dir_size = if metadata.is_dir() { + DirSize::Calculating(Controller::new()) + } else { + DirSize::NotDirectory + }; + Item { name, display_name, @@ -491,7 +498,7 @@ pub fn item_from_entry( selected: false, highlighted: false, overlaps_drag_rect: false, - size: None, + dir_size, } } @@ -721,7 +728,7 @@ pub fn scan_trash(sizes: IconSizes) -> Vec { selected: false, highlighted: false, overlaps_drag_rect: false, - size: None, + dir_size: DirSize::NotDirectory, }); } } @@ -905,7 +912,7 @@ pub fn scan_desktop( selected: false, highlighted: false, overlaps_drag_rect: false, - size: None, + dir_size: DirSize::NotDirectory, }) } @@ -1079,7 +1086,7 @@ pub enum Message { ZoomOut, HighlightDeactivate(usize), HighlightActivate(usize), - DirectorySize(PathBuf, u64), + DirectorySize(PathBuf, DirSize), } #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -1098,6 +1105,14 @@ impl MenuAction for LocationMenuAction { } } +#[derive(Clone, Debug)] +pub enum DirSize { + Calculating(Controller), + Directory(u64), + NotDirectory, + Error(String), +} + #[derive(Clone, Debug)] pub enum ItemMetadata { Path { @@ -1309,7 +1324,7 @@ pub struct Item { pub selected: bool, pub highlighted: bool, pub overlaps_drag_rect: bool, - pub size: Option, + pub dir_size: DirSize, } impl Item { @@ -1409,9 +1424,11 @@ impl Item { ItemMetadata::Path { metadata, children } => { if metadata.is_dir() { details = details.push(widget::text(fl!("items", items = children))); - let size = match self.size { - Some(size) => format_size(size), - None => fl!("calculating"), + let size = match &self.dir_size { + DirSize::Calculating(_) => fl!("calculating"), + DirSize::Directory(size) => format_size(*size), + DirSize::NotDirectory => String::new(), + DirSize::Error(err) => err.clone(), }; details = details.push(widget::text(fl!("item-size", size = size))); } else { @@ -1653,14 +1670,20 @@ pub struct Tab { search_context: Option, } -fn calculate_dir_size(path: &Path) -> u64 { - WalkDir::new(path) - .into_iter() - .filter_map(|entry| entry.ok()) - .filter_map(|entry| entry.metadata().ok()) - .filter(|metadata| metadata.is_file()) - .map(|metadata| metadata.len()) - .sum() +fn calculate_dir_size(path: &Path, controller: Controller) -> Result { + let mut total = 0; + for entry_res in WalkDir::new(path) { + controller.check()?; + //TODO: report more errors? + if let Ok(entry) = entry_res { + if let Ok(metadata) = entry.metadata() { + if metadata.is_file() { + total += metadata.len(); + } + } + } + } + Ok(total) } fn folder_name>(path: P) -> (String, bool) { @@ -2917,13 +2940,13 @@ impl Tab { let location = Location::Path(path); if let Some(ref mut item) = self.parent_item_opt { if item.location_opt.as_ref() == Some(&location) { - item.size = Some(dir_size); + item.dir_size = dir_size.clone(); } } if let Some(ref mut items) = self.items_opt { for item in items.iter_mut() { if item.location_opt.as_ref() == Some(&location) { - item.size = Some(dir_size); + item.dir_size = dir_size; break; } } @@ -4472,53 +4495,69 @@ impl Tab { if preview { // Load directory size for selected items - for item in items + if let Some(item) = items .iter() - .filter(|item| { - // Item must be a selected directory - item.selected && item.metadata.is_dir() - }) - .chain(&self.parent_item_opt) + .filter(|item| item.selected) + .next() + .or(self.parent_item_opt.as_ref()) { // Item must have a path - let Some(path) = item.path_opt().map(|path| path.to_path_buf()) else { - continue; - }; - subscriptions.push(Subscription::run_with_id( - ("dir_size", path.clone()), - stream::channel(1, |mut output| async move { - let message = { - let path = path.clone(); - tokio::task::spawn_blocking(move || { - let start = Instant::now(); - let total_size = calculate_dir_size(&path); - log::debug!( - "calculated directory size of {:?} in {:?}", - path, - start.elapsed() - ); - Message::DirectorySize(path.clone(), total_size) - }) - .await - .unwrap() - }; + if let Some(path) = item.path_opt().map(|path| path.to_path_buf()) { + // Item must be calculating directory size + if let DirSize::Calculating(controller) = &item.dir_size { + let controller = controller.clone(); + subscriptions.push(Subscription::run_with_id( + ("dir_size", path.clone()), + stream::channel(1, |mut output| async move { + let message = { + let path = path.clone(); + tokio::task::spawn_blocking(move || { + let start = Instant::now(); + match calculate_dir_size(&path, controller) { + Ok(size) => { + log::debug!( + "calculated directory size of {:?} in {:?}", + path, + start.elapsed() + ); + Message::DirectorySize( + path.clone(), + DirSize::Directory(size), + ) + } + Err(err) => { + log::warn!( + "failed to calculate directory size of {:?}: {}", + path, + err + ); + Message::DirectorySize( + path.clone(), + DirSize::Error(err), + ) + } + } + }) + .await + .unwrap() + }; - match output.send(message).await { - Ok(()) => {} - Err(err) => { - log::warn!( - "failed to send directory size for {:?}: {}", - &path, - err - ); - } - } + match output.send(message).await { + Ok(()) => {} + Err(err) => { + log::warn!( + "failed to send directory size for {:?}: {}", + &path, + err + ); + } + } - std::future::pending().await - }), - )); - // Only calculate size for one directory - break; + std::future::pending().await + }), + )); + } + } } }