From 3b72d2974054ec0fe89835cb781c7ba7aedc1392 Mon Sep 17 00:00:00 2001 From: Josh Megnauth Date: Fri, 20 Sep 2024 02:27:23 -0400 Subject: [PATCH] Show file deletion date in Trash Closes: #458 --- i18n/en/cosmic_files.ftl | 2 ++ src/menu.rs | 28 ++++++++++++++---- src/tab.rs | 62 +++++++++++++++++++++++++++++++++++----- 3 files changed, 80 insertions(+), 12 deletions(-) diff --git a/i18n/en/cosmic_files.ftl b/i18n/en/cosmic_files.ftl index 77b53b5..e7579d1 100644 --- a/i18n/en/cosmic_files.ftl +++ b/i18n/en/cosmic_files.ftl @@ -14,6 +14,7 @@ today = Today # List view name = Name modified = Modified +trashed-on = Trashed size = Size # Dialogs @@ -198,6 +199,7 @@ remove-from-sidebar = Remove from sidebar sort-by-name = Sort by name sort-by-modified = Sort by modified sort-by-size = Sort by size +sort-by-trashed = Sort by delete time # Menu diff --git a/src/menu.rs b/src/menu.rs index 1cc8739..b65e7fa 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -238,7 +238,7 @@ pub fn context_menu<'a>( } else { // TODO: Nested menu children.push(sort_item(fl!("sort-by-name"), HeadingOptions::Name)); - children.push(sort_item(fl!("sort-by-modified"), HeadingOptions::Modified)); + children.push(sort_item(fl!("sort-by-trashed"), HeadingOptions::TrashedOn)); children.push(sort_item(fl!("sort-by-size"), HeadingOptions::Size)); } } @@ -277,6 +277,7 @@ pub fn dialog_menu<'a>( Action::SetSort(sort, dir), ) }; + let in_trash = tab.location == Location::Trash; MenuBar::new(vec![ menu::Tree::with_children( @@ -313,12 +314,20 @@ pub fn dialog_menu<'a>( sort_item(fl!("sort-z-a"), tab::HeadingOptions::Name, false), sort_item( fl!("sort-newest-first"), - tab::HeadingOptions::Modified, + if in_trash { + tab::HeadingOptions::TrashedOn + } else { + tab::HeadingOptions::Modified + }, false, ), sort_item( fl!("sort-oldest-first"), - tab::HeadingOptions::Modified, + if in_trash { + tab::HeadingOptions::TrashedOn + } else { + tab::HeadingOptions::Modified + }, true, ), sort_item( @@ -355,6 +364,7 @@ pub fn menu_bar<'a>( Action::SetSort(sort, dir), ) }; + let in_trash = tab_opt.map_or(false, |tab| tab.location == Location::Trash); MenuBar::new(vec![ menu::Tree::with_children( @@ -442,12 +452,20 @@ pub fn menu_bar<'a>( sort_item(fl!("sort-z-a"), tab::HeadingOptions::Name, false), sort_item( fl!("sort-newest-first"), - tab::HeadingOptions::Modified, + if in_trash { + tab::HeadingOptions::TrashedOn + } else { + tab::HeadingOptions::Modified + }, false, ), sort_item( fl!("sort-oldest-first"), - tab::HeadingOptions::Modified, + if in_trash { + tab::HeadingOptions::TrashedOn + } else { + tab::HeadingOptions::Modified + }, true, ), sort_item( diff --git a/src/tab.rs b/src/tab.rs index 36742b7..d810eb9 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -51,7 +51,7 @@ use std::{ os::unix::fs::MetadataExt, path::{Path, PathBuf}, sync::{Arc, Mutex}, - time::{Duration, Instant}, + time::{Duration, Instant, SystemTime}, }; use crate::{ @@ -265,6 +265,21 @@ fn format_permissions(metadata: &Metadata, owner: PermissionOwner) -> String { struct FormatTime(std::time::SystemTime); +impl FormatTime { + fn from_secs(secs: i64) -> Option { + // This looks convoluted because we need to ensure the units match up + let secs: u64 = secs.try_into().ok()?; + let now = SystemTime::now(); + let filetime_diff = now + .duration_since(SystemTime::UNIX_EPOCH) + .map(|from_epoch| from_epoch.as_secs()) + .ok() + .and_then(|now_secs| now_secs.checked_sub(secs)) + .map(Duration::from_secs)?; + now.checked_add(filetime_diff).map(FormatTime) + } +} + impl Display for FormatTime { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let date_time = chrono::DateTime::::from(self.0); @@ -1165,6 +1180,7 @@ pub enum HeadingOptions { Name = 0, Modified, Size, + TrashedOn, } impl fmt::Display for HeadingOptions { @@ -1173,6 +1189,7 @@ impl fmt::Display for HeadingOptions { HeadingOptions::Name => write!(f, "{}", fl!("name")), HeadingOptions::Modified => write!(f, "{}", fl!("modified")), HeadingOptions::Size => write!(f, "{}", fl!("size")), + HeadingOptions::TrashedOn => write!(f, "{}", fl!("trashed-on")), } } } @@ -1183,6 +1200,7 @@ impl HeadingOptions { HeadingOptions::Name.to_string(), HeadingOptions::Modified.to_string(), HeadingOptions::Size.to_string(), + HeadingOptions::TrashedOn.to_string(), ] } } @@ -2380,6 +2398,26 @@ impl Tab { } }); } + HeadingOptions::TrashedOn => { + let time_deleted = |x: &Item| match &x.metadata { + ItemMetadata::Trash { entry, .. } => Some(entry.time_deleted), + _ => None, + }; + + items.sort_by(|a, b| { + let a_time_deleted = time_deleted(a.1); + let b_time_deleted = time_deleted(b.1); + if self.config.folders_first { + match (a.1.metadata.is_dir(), b.1.metadata.is_dir()) { + (true, false) => Ordering::Less, + (false, true) => Ordering::Greater, + _ => check_reverse(a_time_deleted.cmp(&b_time_deleted), heading_sort), + } + } else { + check_reverse(b_time_deleted.cmp(&a_time_deleted), heading_sort) + } + }); + } } Some(items) } @@ -2540,12 +2578,19 @@ impl Tab { let heading_row = widget::row::with_children(vec![ heading_item(fl!("name"), Length::Fill, HeadingOptions::Name), - //TODO: do not show modified column when in the trash - heading_item( - fl!("modified"), - Length::Fixed(modified_width), - HeadingOptions::Modified, - ), + if self.location == Location::Trash { + heading_item( + fl!("trashed-on"), + Length::Fixed(modified_width), + HeadingOptions::TrashedOn, + ) + } else { + heading_item( + fl!("modified"), + Length::Fixed(modified_width), + HeadingOptions::Modified, + ) + }, heading_item(fl!("size"), Length::Fixed(size_width), HeadingOptions::Size), ]) .align_items(Alignment::Center) @@ -3159,6 +3204,9 @@ impl Tab { Ok(time) => format_time(time).to_string(), Err(_) => String::new(), }, + ItemMetadata::Trash { entry, .. } => FormatTime::from_secs(entry.time_deleted) + .map(|t| t.to_string()) + .unwrap_or_default(), _ => String::new(), };