Refactor trash handling to improve portability
This commit is contained in:
parent
33890633b5
commit
9c0eb63b82
7 changed files with 173 additions and 166 deletions
22
src/app.rs
22
src/app.rs
|
|
@ -99,6 +99,7 @@ use crate::{
|
|||
self, HOVER_DURATION, HeadingOptions, ItemMetadata, Location, SORT_OPTION_FALLBACK,
|
||||
SearchLocation, Tab,
|
||||
},
|
||||
trash::{Trash, TrashExt},
|
||||
zoom::{zoom_in_view, zoom_out_view, zoom_to_default},
|
||||
};
|
||||
|
||||
|
|
@ -1794,7 +1795,7 @@ impl App {
|
|||
|
||||
nav_model = nav_model.insert(|b| {
|
||||
b.text(fl!("trash"))
|
||||
.icon(icon::icon(tab::trash_helpers::trash_icon_symbolic(16)))
|
||||
.icon(icon::icon(Trash::icon_symbolic(16)))
|
||||
.data(Location::Trash)
|
||||
.divider_above()
|
||||
});
|
||||
|
|
@ -2655,9 +2656,7 @@ impl Application for App {
|
|||
));
|
||||
}
|
||||
|
||||
if matches!(location_opt, Some(Location::Trash))
|
||||
&& !trash::os_limited::is_empty().unwrap_or(true)
|
||||
{
|
||||
if matches!(location_opt, Some(Location::Trash)) && !Trash::is_empty() {
|
||||
items.push(cosmic::widget::menu::Item::Button(
|
||||
fl!("empty-trash"),
|
||||
None,
|
||||
|
|
@ -4237,10 +4236,8 @@ impl Application for App {
|
|||
.is_some_and(|loc| matches!(loc, Location::Trash))
|
||||
});
|
||||
if let Some(entity) = maybe_entity {
|
||||
self.nav_model.icon_set(
|
||||
entity,
|
||||
icon::icon(tab::trash_helpers::trash_icon_symbolic(16)),
|
||||
);
|
||||
self.nav_model
|
||||
.icon_set(entity, icon::icon(Trash::icon_symbolic(16)));
|
||||
}
|
||||
|
||||
return Task::batch([self.rescan_trash(), self.update_desktop()]);
|
||||
|
|
@ -6803,14 +6800,7 @@ impl Application for App {
|
|||
},
|
||||
);
|
||||
|
||||
// TODO: Trash watching support for Windows, macOS, and other OSes
|
||||
#[cfg(all(
|
||||
unix,
|
||||
not(target_os = "macos"),
|
||||
not(target_os = "ios"),
|
||||
not(target_os = "android")
|
||||
))]
|
||||
match (watcher_res, trash::os_limited::trash_folders()) {
|
||||
match (watcher_res, Trash::folders()) {
|
||||
(Ok(mut watcher), Ok(trash_bins)) => {
|
||||
// Watch the "bins" themselves as well as the files folder where
|
||||
// trashed items are placed. This allows us to avoid recursively
|
||||
|
|
|
|||
|
|
@ -441,13 +441,8 @@ impl<M: Send + 'static> Dialog<M> {
|
|||
|
||||
#[derive(Clone, Debug)]
|
||||
enum DialogPage {
|
||||
NewFolder {
|
||||
parent: PathBuf,
|
||||
name: String,
|
||||
},
|
||||
Replace {
|
||||
filename: String,
|
||||
},
|
||||
NewFolder { parent: PathBuf, name: String },
|
||||
Replace { filename: String },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
@ -2042,7 +2037,7 @@ impl Application for App {
|
|||
}
|
||||
|
||||
col = col.push(
|
||||
self.tab
|
||||
self.tab
|
||||
.view(&self.key_binds, &self.modifiers, false, &[])
|
||||
.map(Message::TabMessage),
|
||||
);
|
||||
|
|
|
|||
16
src/lib.rs
16
src/lib.rs
|
|
@ -5,14 +5,18 @@ use cosmic::{app::Settings, iced::Limits};
|
|||
use std::{env, fs, path::PathBuf, process};
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||
|
||||
use app::{App, Flags};
|
||||
use crate::{
|
||||
app::{App, Flags},
|
||||
config::{Config, State},
|
||||
tab::Location,
|
||||
};
|
||||
|
||||
pub mod app;
|
||||
mod archive;
|
||||
pub mod channel;
|
||||
pub mod clipboard;
|
||||
mod context_action;
|
||||
use config::Config;
|
||||
pub mod config;
|
||||
mod context_action;
|
||||
pub mod dialog;
|
||||
mod key_bind;
|
||||
pub(crate) mod large_image;
|
||||
|
|
@ -25,13 +29,11 @@ mod mounter;
|
|||
mod mouse_area;
|
||||
pub mod operation;
|
||||
mod spawn_detached;
|
||||
use tab::Location;
|
||||
mod zoom;
|
||||
|
||||
use crate::config::State;
|
||||
pub mod tab;
|
||||
mod thumbnail_cacher;
|
||||
mod thumbnailer;
|
||||
pub(crate) mod trash;
|
||||
mod zoom;
|
||||
|
||||
pub(crate) type FxOrderMap<K, V> = ordermap::OrderMap<K, V, rustc_hash::FxBuildHasher>;
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ use crate::{
|
|||
config::{Config, ContextActionPreset},
|
||||
fl,
|
||||
tab::{self, HeadingOptions, Location, LocationMenuAction, SearchLocation, Tab},
|
||||
trash::{Trash, TrashExt},
|
||||
};
|
||||
|
||||
static MENU_ID: LazyLock<cosmic::widget::Id> =
|
||||
|
|
@ -192,7 +193,7 @@ pub fn context_menu<'a>(
|
|||
) => {
|
||||
if selected_trash_only {
|
||||
children.push(menu_item(fl!("open"), Action::Open).into());
|
||||
if !trash::os_limited::is_empty().unwrap_or(true) {
|
||||
if !Trash::is_empty() {
|
||||
children.push(menu_item(fl!("empty-trash"), Action::EmptyTrash).into());
|
||||
}
|
||||
} else if let Some(entry) = selected_desktop_entry {
|
||||
|
|
|
|||
|
|
@ -1125,7 +1125,9 @@ impl Operation {
|
|||
#[cfg(target_os = "macos")]
|
||||
Self::Restore { .. } => {
|
||||
// TODO: add support for macos
|
||||
return OperationError::from_msg("Restoring from trash is not supported on macos");
|
||||
return Err(OperationError::from_msg(
|
||||
"Restoring from trash is not supported on macos",
|
||||
));
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
Self::Restore { items } => {
|
||||
|
|
|
|||
140
src/tab.rs
140
src/tab.rs
|
|
@ -39,7 +39,6 @@ use icu::{
|
|||
use image::{DynamicImage, ImageReader};
|
||||
use jiff_icu::ConvertFrom;
|
||||
use mime_guess::{Mime, mime};
|
||||
use regex::Regex;
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
|
|
@ -84,6 +83,7 @@ use crate::{
|
|||
operation::{Controller, OperationError},
|
||||
thumbnail_cacher::{CachedThumbnail, ThumbnailCacher, ThumbnailSize},
|
||||
thumbnailer::thumbnailer,
|
||||
trash::{Trash, TrashExt},
|
||||
};
|
||||
use uzers::{get_group_by_gid, get_user_by_uid};
|
||||
|
||||
|
|
@ -1168,133 +1168,7 @@ pub fn scan_search<F: Fn(SearchItem) -> bool + Sync>(
|
|||
}
|
||||
}
|
||||
SearchLocation::Trash => {
|
||||
trash_helpers::scan_search_trash(callback, ®ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This config statement is from trash::os_limited, inverted
|
||||
#[cfg(not(any(
|
||||
target_os = "windows",
|
||||
all(
|
||||
unix,
|
||||
not(target_os = "macos"),
|
||||
not(target_os = "ios"),
|
||||
not(target_os = "android")
|
||||
)
|
||||
)))]
|
||||
mod trash_helpers {
|
||||
use super::*;
|
||||
|
||||
pub fn trash_entries() -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
pub fn trash_icon(icon_size: u16) -> widget::icon::Handle {
|
||||
widget::icon::from_name("user-trash")
|
||||
.size(icon_size)
|
||||
.handle()
|
||||
}
|
||||
|
||||
pub fn trash_icon_symbolic(icon_size: u16) -> widget::icon::Handle {
|
||||
widget::icon::from_name("user-trash-symbolic")
|
||||
.size(icon_size)
|
||||
.handle()
|
||||
}
|
||||
|
||||
pub fn scan_trash(_sizes: IconSizes) -> Vec<Item> {
|
||||
log::warn!("viewing trash not supported on this platform");
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
pub fn scan_search_trash<F: Fn(SearchItem) -> bool + Sync>(callback: F, regex: &Regex) {}
|
||||
}
|
||||
|
||||
// This config statement is from trash::os_limited
|
||||
#[cfg(any(
|
||||
target_os = "windows",
|
||||
all(
|
||||
unix,
|
||||
not(target_os = "macos"),
|
||||
not(target_os = "ios"),
|
||||
not(target_os = "android")
|
||||
)
|
||||
))]
|
||||
pub mod trash_helpers {
|
||||
use super::*;
|
||||
|
||||
pub fn trash_entries() -> usize {
|
||||
match trash::os_limited::list() {
|
||||
Ok(entries) => entries.len(),
|
||||
Err(_err) => 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trash_icon(icon_size: u16) -> widget::icon::Handle {
|
||||
widget::icon::from_name(if trash::os_limited::is_empty().unwrap_or(true) {
|
||||
"user-trash"
|
||||
} else {
|
||||
"user-trash-full"
|
||||
})
|
||||
.size(icon_size)
|
||||
.handle()
|
||||
}
|
||||
|
||||
pub fn trash_icon_symbolic(icon_size: u16) -> widget::icon::Handle {
|
||||
widget::icon::from_name(if trash::os_limited::is_empty().unwrap_or(true) {
|
||||
"user-trash-symbolic"
|
||||
} else {
|
||||
"user-trash-full-symbolic"
|
||||
})
|
||||
.size(icon_size)
|
||||
.handle()
|
||||
}
|
||||
|
||||
pub fn scan_trash(sizes: IconSizes) -> Vec<Item> {
|
||||
let entries = match trash::os_limited::list() {
|
||||
Ok(entry) => entry,
|
||||
Err(err) => {
|
||||
log::warn!("failed to read trash items: {err}");
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
let mut items: Vec<_> = entries
|
||||
.into_iter()
|
||||
.filter_map(|entry| {
|
||||
let metadata = trash::os_limited::metadata(&entry)
|
||||
.inspect_err(|err| {
|
||||
log::warn!("failed to get metadata for trash item {entry:?}: {err}")
|
||||
})
|
||||
.ok()?;
|
||||
Some(item_from_trash_entry(entry, metadata, sizes))
|
||||
})
|
||||
.collect();
|
||||
items.sort_by(|a, b| match (a.metadata.is_dir(), b.metadata.is_dir()) {
|
||||
(true, false) => Ordering::Less,
|
||||
(false, true) => Ordering::Greater,
|
||||
_ => LANGUAGE_SORTER.compare(&a.display_name, &b.display_name),
|
||||
});
|
||||
items
|
||||
}
|
||||
|
||||
pub fn scan_search_trash<F: Fn(SearchItem) -> bool + Sync>(callback: F, regex: &Regex) {
|
||||
let entries = match trash::os_limited::list() {
|
||||
Ok(entries) => entries,
|
||||
Err(err) => {
|
||||
log::warn!("failed to read trash items: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
for entry in entries {
|
||||
if let Ok(metadata) = trash::os_limited::metadata(&entry).inspect_err(|err| {
|
||||
log::warn!("failed to get metadata for trash item {entry:?}: {err}")
|
||||
}) {
|
||||
let name = entry.name.to_string_lossy();
|
||||
if regex.is_match(&name) && !callback(SearchItem::Trash(entry, metadata)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Trash::scan_search(callback, ®ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1432,15 +1306,15 @@ pub fn scan_desktop(
|
|||
let display_name = Item::display_name(&name);
|
||||
|
||||
let metadata = ItemMetadata::SimpleDir {
|
||||
entries: trash_helpers::trash_entries() as u64,
|
||||
entries: Trash::entries() as u64,
|
||||
};
|
||||
|
||||
let (mime, icon_handle_grid, icon_handle_list, icon_handle_list_condensed) = {
|
||||
(
|
||||
"inode/directory".parse().unwrap(),
|
||||
trash_helpers::trash_icon(sizes.grid()),
|
||||
trash_helpers::trash_icon(sizes.list()),
|
||||
trash_helpers::trash_icon(sizes.list_condensed()),
|
||||
Trash::icon(sizes.grid()),
|
||||
Trash::icon(sizes.list()),
|
||||
Trash::icon(sizes.list_condensed()),
|
||||
)
|
||||
};
|
||||
|
||||
|
|
@ -1681,7 +1555,7 @@ impl Location {
|
|||
// Search is done incrementally
|
||||
Vec::new()
|
||||
}
|
||||
Self::Trash => trash_helpers::scan_trash(sizes),
|
||||
Self::Trash => Trash::scan(sizes),
|
||||
Self::Recents => scan_recents(sizes),
|
||||
Self::Network(uri, _, _) => scan_network(uri, sizes),
|
||||
};
|
||||
|
|
|
|||
143
src/trash.rs
Normal file
143
src/trash.rs
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
use cosmic::widget;
|
||||
use regex::Regex;
|
||||
use std::{collections::HashSet, path::PathBuf};
|
||||
|
||||
use crate::{
|
||||
config::IconSizes,
|
||||
tab::{Item, SearchItem},
|
||||
};
|
||||
|
||||
pub trait TrashExt {
|
||||
fn is_empty() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn entries() -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
fn folders() -> Result<HashSet<PathBuf>, trash::Error> {
|
||||
Err(trash::Error::Unknown {
|
||||
description: "reading trash folders not supported on this platform".into(),
|
||||
})
|
||||
}
|
||||
|
||||
fn scan(_sizes: IconSizes) -> Vec<Item> {
|
||||
log::warn!("viewing trash not supported on this platform");
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn scan_search<F: Fn(SearchItem) -> bool + Sync>(callback: F, regex: &Regex) {}
|
||||
|
||||
fn icon(icon_size: u16) -> widget::icon::Handle {
|
||||
widget::icon::from_name(if Self::is_empty() {
|
||||
"user-trash"
|
||||
} else {
|
||||
"user-trash-full"
|
||||
})
|
||||
.size(icon_size)
|
||||
.handle()
|
||||
}
|
||||
|
||||
fn icon_symbolic(icon_size: u16) -> widget::icon::Handle {
|
||||
widget::icon::from_name(if Self::is_empty() {
|
||||
"user-trash-symbolic"
|
||||
} else {
|
||||
"user-trash-full-symbolic"
|
||||
})
|
||||
.size(icon_size)
|
||||
.handle()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Trash;
|
||||
|
||||
// This config statement is from trash::os_limited
|
||||
#[cfg(any(
|
||||
target_os = "windows",
|
||||
all(
|
||||
unix,
|
||||
not(target_os = "macos"),
|
||||
not(target_os = "ios"),
|
||||
not(target_os = "android")
|
||||
)
|
||||
))]
|
||||
impl TrashExt for Trash {
|
||||
fn is_empty() -> bool {
|
||||
trash::os_limited::is_empty().unwrap_or(true)
|
||||
}
|
||||
|
||||
fn entries() -> usize {
|
||||
match trash::os_limited::list() {
|
||||
Ok(entries) => entries.len(),
|
||||
Err(_err) => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn folders() -> Result<HashSet<PathBuf>, trash::Error> {
|
||||
trash::os_limited::trash_folders()
|
||||
}
|
||||
|
||||
fn scan(sizes: IconSizes) -> Vec<Item> {
|
||||
use crate::{localize::LANGUAGE_SORTER, tab::item_from_trash_entry};
|
||||
use std::cmp::Ordering;
|
||||
|
||||
let entries = match trash::os_limited::list() {
|
||||
Ok(entry) => entry,
|
||||
Err(err) => {
|
||||
log::warn!("failed to read trash items: {err}");
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
let mut items: Vec<_> = entries
|
||||
.into_iter()
|
||||
.filter_map(|entry| {
|
||||
let metadata = trash::os_limited::metadata(&entry)
|
||||
.inspect_err(|err| {
|
||||
log::warn!("failed to get metadata for trash item {entry:?}: {err}")
|
||||
})
|
||||
.ok()?;
|
||||
Some(item_from_trash_entry(entry, metadata, sizes))
|
||||
})
|
||||
.collect();
|
||||
items.sort_by(|a, b| match (a.metadata.is_dir(), b.metadata.is_dir()) {
|
||||
(true, false) => Ordering::Less,
|
||||
(false, true) => Ordering::Greater,
|
||||
_ => LANGUAGE_SORTER.compare(&a.display_name, &b.display_name),
|
||||
});
|
||||
items
|
||||
}
|
||||
|
||||
fn scan_search<F: Fn(SearchItem) -> bool + Sync>(callback: F, regex: &Regex) {
|
||||
let entries = match trash::os_limited::list() {
|
||||
Ok(entries) => entries,
|
||||
Err(err) => {
|
||||
log::warn!("failed to read trash items: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
for entry in entries {
|
||||
if let Ok(metadata) = trash::os_limited::metadata(&entry).inspect_err(|err| {
|
||||
log::warn!("failed to get metadata for trash item {entry:?}: {err}")
|
||||
}) {
|
||||
let name = entry.name.to_string_lossy();
|
||||
if regex.is_match(&name) && !callback(SearchItem::Trash(entry, metadata)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This config statement is from trash::os_limited, inverted
|
||||
#[cfg(not(any(
|
||||
target_os = "windows",
|
||||
all(
|
||||
unix,
|
||||
not(target_os = "macos"),
|
||||
not(target_os = "ios"),
|
||||
not(target_os = "android")
|
||||
)
|
||||
)))]
|
||||
impl TrashExt for Trash {}
|
||||
Loading…
Add table
Add a link
Reference in a new issue