Refactor directory size to allow for cancellation
This commit is contained in:
parent
6c0c89e1f7
commit
2f08c05afe
3 changed files with 124 additions and 63 deletions
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
159
src/tab.rs
159
src/tab.rs
|
|
@ -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;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue