Refactor directory size to allow for cancellation

This commit is contained in:
Jeremy Soller 2024-11-15 17:30:25 -07:00
parent 6c0c89e1f7
commit 2f08c05afe
No known key found for this signature in database
GPG key ID: D02FD439211AF56F
3 changed files with 124 additions and 63 deletions

View file

@ -10,7 +10,7 @@ use super::{Mounter, MounterAuth, MounterItem, MounterItems, MounterMessage};
use crate::{ use crate::{
config::IconSizes, config::IconSizes,
err_str, err_str,
tab::{self, ItemMetadata, ItemThumbnail, Location}, tab::{self, DirSize, ItemMetadata, ItemThumbnail, Location},
}; };
fn gio_icon_to_path(icon: &gio::Icon, size: u16) -> Option<PathBuf> { fn gio_icon_to_path(icon: &gio::Icon, size: u16) -> Option<PathBuf> {
@ -135,7 +135,8 @@ fn network_scan(uri: &str, sizes: IconSizes) -> Result<Vec<tab::Item>, String> {
selected: false, selected: false,
highlighted: false, highlighted: false,
overlaps_drag_rect: false, overlaps_drag_rect: false,
size: None, //TODO: scan directory size on gvfs mounts?
dir_size: DirSize::NotDirectory,
}); });
} }
Ok(items) Ok(items)

View file

@ -231,19 +231,22 @@ pub enum ControllerState {
Running, Running,
} }
#[derive(Debug)]
struct ControllerInner { struct ControllerInner {
state: Mutex<ControllerState>, state: Mutex<ControllerState>,
condvar: Condvar, condvar: Condvar,
} }
#[derive(Clone)] #[derive(Debug)]
pub struct Controller { pub struct Controller {
primary: bool,
inner: Arc<ControllerInner>, inner: Arc<ControllerInner>,
} }
impl Controller { impl Controller {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
primary: true,
inner: Arc::new(ControllerInner { inner: Arc::new(ControllerInner {
state: Mutex::new(ControllerState::Running), state: Mutex::new(ControllerState::Running),
condvar: Condvar::new(), 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)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum ReplaceResult { pub enum ReplaceResult {
Replace(bool), Replace(bool),

View file

@ -69,6 +69,7 @@ use crate::{
mime_icon::{mime_for_path, mime_icon}, mime_icon::{mime_for_path, mime_icon},
mounter::MOUNTERS, mounter::MOUNTERS,
mouse_area, mouse_area,
operation::Controller,
thumbnailer::thumbnailer, thumbnailer::thumbnailer,
}; };
use unix_permissions_ext::UNIXPermissionsExt; use unix_permissions_ext::UNIXPermissionsExt;
@ -473,6 +474,12 @@ pub fn item_from_entry(
0 0
}; };
let dir_size = if metadata.is_dir() {
DirSize::Calculating(Controller::new())
} else {
DirSize::NotDirectory
};
Item { Item {
name, name,
display_name, display_name,
@ -491,7 +498,7 @@ pub fn item_from_entry(
selected: false, selected: false,
highlighted: false, highlighted: false,
overlaps_drag_rect: false, overlaps_drag_rect: false,
size: None, dir_size,
} }
} }
@ -721,7 +728,7 @@ pub fn scan_trash(sizes: IconSizes) -> Vec<Item> {
selected: false, selected: false,
highlighted: false, highlighted: false,
overlaps_drag_rect: false, overlaps_drag_rect: false,
size: None, dir_size: DirSize::NotDirectory,
}); });
} }
} }
@ -905,7 +912,7 @@ pub fn scan_desktop(
selected: false, selected: false,
highlighted: false, highlighted: false,
overlaps_drag_rect: false, overlaps_drag_rect: false,
size: None, dir_size: DirSize::NotDirectory,
}) })
} }
@ -1079,7 +1086,7 @@ pub enum Message {
ZoomOut, ZoomOut,
HighlightDeactivate(usize), HighlightDeactivate(usize),
HighlightActivate(usize), HighlightActivate(usize),
DirectorySize(PathBuf, u64), DirectorySize(PathBuf, DirSize),
} }
#[derive(Copy, Clone, Debug, Eq, PartialEq)] #[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)] #[derive(Clone, Debug)]
pub enum ItemMetadata { pub enum ItemMetadata {
Path { Path {
@ -1309,7 +1324,7 @@ pub struct Item {
pub selected: bool, pub selected: bool,
pub highlighted: bool, pub highlighted: bool,
pub overlaps_drag_rect: bool, pub overlaps_drag_rect: bool,
pub size: Option<u64>, pub dir_size: DirSize,
} }
impl Item { impl Item {
@ -1409,9 +1424,11 @@ impl Item {
ItemMetadata::Path { metadata, children } => { ItemMetadata::Path { metadata, children } => {
if metadata.is_dir() { if metadata.is_dir() {
details = details.push(widget::text(fl!("items", items = children))); details = details.push(widget::text(fl!("items", items = children)));
let size = match self.size { let size = match &self.dir_size {
Some(size) => format_size(size), DirSize::Calculating(_) => fl!("calculating"),
None => 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))); details = details.push(widget::text(fl!("item-size", size = size)));
} else { } else {
@ -1653,14 +1670,20 @@ pub struct Tab {
search_context: Option<SearchContext>, search_context: Option<SearchContext>,
} }
fn calculate_dir_size(path: &Path) -> u64 { fn calculate_dir_size(path: &Path, controller: Controller) -> Result<u64, String> {
WalkDir::new(path) let mut total = 0;
.into_iter() for entry_res in WalkDir::new(path) {
.filter_map(|entry| entry.ok()) controller.check()?;
.filter_map(|entry| entry.metadata().ok()) //TODO: report more errors?
.filter(|metadata| metadata.is_file()) if let Ok(entry) = entry_res {
.map(|metadata| metadata.len()) if let Ok(metadata) = entry.metadata() {
.sum() if metadata.is_file() {
total += metadata.len();
}
}
}
}
Ok(total)
} }
fn folder_name<P: AsRef<Path>>(path: P) -> (String, bool) { fn folder_name<P: AsRef<Path>>(path: P) -> (String, bool) {
@ -2917,13 +2940,13 @@ impl Tab {
let location = Location::Path(path); let location = Location::Path(path);
if let Some(ref mut item) = self.parent_item_opt { if let Some(ref mut item) = self.parent_item_opt {
if item.location_opt.as_ref() == Some(&location) { 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 { if let Some(ref mut items) = self.items_opt {
for item in items.iter_mut() { for item in items.iter_mut() {
if item.location_opt.as_ref() == Some(&location) { if item.location_opt.as_ref() == Some(&location) {
item.size = Some(dir_size); item.dir_size = dir_size;
break; break;
} }
} }
@ -4472,53 +4495,69 @@ impl Tab {
if preview { if preview {
// Load directory size for selected items // Load directory size for selected items
for item in items if let Some(item) = items
.iter() .iter()
.filter(|item| { .filter(|item| item.selected)
// Item must be a selected directory .next()
item.selected && item.metadata.is_dir() .or(self.parent_item_opt.as_ref())
})
.chain(&self.parent_item_opt)
{ {
// Item must have a path // Item must have a path
let Some(path) = item.path_opt().map(|path| path.to_path_buf()) else { if let Some(path) = item.path_opt().map(|path| path.to_path_buf()) {
continue; // Item must be calculating directory size
}; if let DirSize::Calculating(controller) = &item.dir_size {
subscriptions.push(Subscription::run_with_id( let controller = controller.clone();
("dir_size", path.clone()), subscriptions.push(Subscription::run_with_id(
stream::channel(1, |mut output| async move { ("dir_size", path.clone()),
let message = { stream::channel(1, |mut output| async move {
let path = path.clone(); let message = {
tokio::task::spawn_blocking(move || { let path = path.clone();
let start = Instant::now(); tokio::task::spawn_blocking(move || {
let total_size = calculate_dir_size(&path); let start = Instant::now();
log::debug!( match calculate_dir_size(&path, controller) {
"calculated directory size of {:?} in {:?}", Ok(size) => {
path, log::debug!(
start.elapsed() "calculated directory size of {:?} in {:?}",
); path,
Message::DirectorySize(path.clone(), total_size) start.elapsed()
}) );
.await Message::DirectorySize(
.unwrap() 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 { match output.send(message).await {
Ok(()) => {} Ok(()) => {}
Err(err) => { Err(err) => {
log::warn!( log::warn!(
"failed to send directory size for {:?}: {}", "failed to send directory size for {:?}: {}",
&path, &path,
err err
); );
} }
} }
std::future::pending().await std::future::pending().await
}), }),
)); ));
// Only calculate size for one directory }
break; }
} }
} }