Merge pull request #1753 from pop-os/epoch-update
Epoch 1.0.11 version update
This commit is contained in:
commit
b3af8bf211
15 changed files with 452 additions and 374 deletions
348
Cargo.lock
generated
348
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cosmic-files"
|
name = "cosmic-files"
|
||||||
version = "1.0.9"
|
version = "1.0.11"
|
||||||
authors = ["Jeremy Soller <jeremy@system76.com>"]
|
authors = ["Jeremy Soller <jeremy@system76.com>"]
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
|
|
@ -55,7 +55,6 @@ rust-embed = "8"
|
||||||
slotmap = "1.1.1"
|
slotmap = "1.1.1"
|
||||||
recently-used-xbel = "1.2.0"
|
recently-used-xbel = "1.2.0"
|
||||||
zip = "8"
|
zip = "8"
|
||||||
uzers = "0.12.2"
|
|
||||||
md-5 = "0.10.6"
|
md-5 = "0.10.6"
|
||||||
png = "0.18"
|
png = "0.18"
|
||||||
jxl-oxide = { version = "0.12.5", features = ["image"] }
|
jxl-oxide = { version = "0.12.5", features = ["image"] }
|
||||||
|
|
@ -81,7 +80,6 @@ features = [
|
||||||
"about",
|
"about",
|
||||||
"advanced-shaping",
|
"advanced-shaping",
|
||||||
"autosize",
|
"autosize",
|
||||||
"desktop",
|
|
||||||
"multi-window",
|
"multi-window",
|
||||||
"tokio",
|
"tokio",
|
||||||
"winit",
|
"winit",
|
||||||
|
|
@ -113,7 +111,7 @@ default = [
|
||||||
"wgpu",
|
"wgpu",
|
||||||
]
|
]
|
||||||
dbus-config = ["libcosmic/dbus-config"]
|
dbus-config = ["libcosmic/dbus-config"]
|
||||||
desktop = ["dep:cosmic-mime-apps", "dep:xdg"]
|
desktop = ["libcosmic/desktop", "dep:cosmic-mime-apps", "dep:xdg"]
|
||||||
desktop-applet = []
|
desktop-applet = []
|
||||||
gvfs = ["dep:gio", "dep:glib"]
|
gvfs = ["dep:gio", "dep:glib"]
|
||||||
io-uring = ["compio/io-uring"]
|
io-uring = ["compio/io-uring"]
|
||||||
|
|
@ -131,6 +129,7 @@ debug = true
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
fork = "0.7"
|
fork = "0.7"
|
||||||
|
uzers = "0.12.2"
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
procfs = "0.18"
|
procfs = "0.18"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cosmic-files-applet"
|
name = "cosmic-files-applet"
|
||||||
version = "1.0.9"
|
version = "1.0.11"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
|
||||||
6
debian/changelog
vendored
6
debian/changelog
vendored
|
|
@ -1,3 +1,9 @@
|
||||||
|
cosmic-files (1.0.11) noble; urgency=medium
|
||||||
|
|
||||||
|
* Epoch 1.0.11 version update
|
||||||
|
|
||||||
|
-- Jeremy Soller <jeremy@system76.com> Tue, 14 Apr 2026 11:09:44 -0600
|
||||||
|
|
||||||
cosmic-files (1.0.9) noble; urgency=medium
|
cosmic-files (1.0.9) noble; urgency=medium
|
||||||
|
|
||||||
* Epoch 1.0.9 version update
|
* Epoch 1.0.9 version update
|
||||||
|
|
|
||||||
38
src/app.rs
38
src/app.rs
|
|
@ -18,9 +18,7 @@ use cosmic::{
|
||||||
Application, ApplicationExt, Element,
|
Application, ApplicationExt, Element,
|
||||||
app::{self, Core, Task, context_drawer},
|
app::{self, Core, Task, context_drawer},
|
||||||
cosmic_config::{self, ConfigSet},
|
cosmic_config::{self, ConfigSet},
|
||||||
cosmic_theme,
|
cosmic_theme, executor,
|
||||||
desktop::fde::DesktopEntry,
|
|
||||||
executor,
|
|
||||||
iced::core::widget::operation::focusable::unfocus,
|
iced::core::widget::operation::focusable::unfocus,
|
||||||
iced::runtime::{clipboard, task},
|
iced::runtime::{clipboard, task},
|
||||||
iced::widget::{button::focus, scrollable::AbsoluteOffset},
|
iced::widget::{button::focus, scrollable::AbsoluteOffset},
|
||||||
|
|
@ -99,6 +97,7 @@ use crate::{
|
||||||
self, HOVER_DURATION, HeadingOptions, ItemMetadata, Location, SORT_OPTION_FALLBACK,
|
self, HOVER_DURATION, HeadingOptions, ItemMetadata, Location, SORT_OPTION_FALLBACK,
|
||||||
SearchLocation, Tab,
|
SearchLocation, Tab,
|
||||||
},
|
},
|
||||||
|
trash::{Trash, TrashExt},
|
||||||
zoom::{zoom_in_view, zoom_out_view, zoom_to_default},
|
zoom::{zoom_in_view, zoom_out_view, zoom_to_default},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -834,9 +833,12 @@ impl App {
|
||||||
|
|
||||||
// First launch apps that can be launched directly
|
// First launch apps that can be launched directly
|
||||||
if mime == "application/x-desktop" {
|
if mime == "application/x-desktop" {
|
||||||
// Try opening desktop application
|
#[cfg(feature = "desktop")]
|
||||||
Self::launch_desktop_entries(&paths);
|
{
|
||||||
continue;
|
// Try opening desktop application
|
||||||
|
Self::launch_desktop_entries(&paths);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
} else if mime == "application/x-executable" || mime == "application/vnd.appimage" {
|
} else if mime == "application/x-executable" || mime == "application/vnd.appimage" {
|
||||||
// Try opening executable
|
// Try opening executable
|
||||||
for path in paths {
|
for path in paths {
|
||||||
|
|
@ -897,7 +899,10 @@ impl App {
|
||||||
Task::batch(tasks)
|
Task::batch(tasks)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "desktop")]
|
||||||
fn launch_desktop_entries(paths: &[impl AsRef<Path>]) {
|
fn launch_desktop_entries(paths: &[impl AsRef<Path>]) {
|
||||||
|
use cosmic::desktop::fde::DesktopEntry;
|
||||||
|
|
||||||
for path in paths.iter().map(AsRef::as_ref) {
|
for path in paths.iter().map(AsRef::as_ref) {
|
||||||
match DesktopEntry::from_path::<&str>(path, None) {
|
match DesktopEntry::from_path::<&str>(path, None) {
|
||||||
Ok(entry) => match entry.exec() {
|
Ok(entry) => match entry.exec() {
|
||||||
|
|
@ -1794,7 +1799,7 @@ impl App {
|
||||||
|
|
||||||
nav_model = nav_model.insert(|b| {
|
nav_model = nav_model.insert(|b| {
|
||||||
b.text(fl!("trash"))
|
b.text(fl!("trash"))
|
||||||
.icon(icon::icon(tab::trash_helpers::trash_icon_symbolic(16)))
|
.icon(icon::icon(Trash::icon_symbolic(16)))
|
||||||
.data(Location::Trash)
|
.data(Location::Trash)
|
||||||
.divider_above()
|
.divider_above()
|
||||||
});
|
});
|
||||||
|
|
@ -2655,9 +2660,7 @@ impl Application for App {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches!(location_opt, Some(Location::Trash))
|
if matches!(location_opt, Some(Location::Trash)) && !Trash::is_empty() {
|
||||||
&& !trash::os_limited::is_empty().unwrap_or(true)
|
|
||||||
{
|
|
||||||
items.push(cosmic::widget::menu::Item::Button(
|
items.push(cosmic::widget::menu::Item::Button(
|
||||||
fl!("empty-trash"),
|
fl!("empty-trash"),
|
||||||
None,
|
None,
|
||||||
|
|
@ -4237,10 +4240,8 @@ impl Application for App {
|
||||||
.is_some_and(|loc| matches!(loc, Location::Trash))
|
.is_some_and(|loc| matches!(loc, Location::Trash))
|
||||||
});
|
});
|
||||||
if let Some(entity) = maybe_entity {
|
if let Some(entity) = maybe_entity {
|
||||||
self.nav_model.icon_set(
|
self.nav_model
|
||||||
entity,
|
.icon_set(entity, icon::icon(Trash::icon_symbolic(16)));
|
||||||
icon::icon(tab::trash_helpers::trash_icon_symbolic(16)),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task::batch([self.rescan_trash(), self.update_desktop()]);
|
return Task::batch([self.rescan_trash(), self.update_desktop()]);
|
||||||
|
|
@ -6803,14 +6804,7 @@ impl Application for App {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: Trash watching support for Windows, macOS, and other OSes
|
match (watcher_res, Trash::folders()) {
|
||||||
#[cfg(all(
|
|
||||||
unix,
|
|
||||||
not(target_os = "macos"),
|
|
||||||
not(target_os = "ios"),
|
|
||||||
not(target_os = "android")
|
|
||||||
))]
|
|
||||||
match (watcher_res, trash::os_limited::trash_folders()) {
|
|
||||||
(Ok(mut watcher), Ok(trash_bins)) => {
|
(Ok(mut watcher), Ok(trash_bins)) => {
|
||||||
// Watch the "bins" themselves as well as the files folder where
|
// Watch the "bins" themselves as well as the files folder where
|
||||||
// trashed items are placed. This allows us to avoid recursively
|
// trashed items are placed. This allows us to avoid recursively
|
||||||
|
|
|
||||||
|
|
@ -189,6 +189,8 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
|
||||||
if file.is_symlink() && (cfg!(unix) || cfg!(windows)) {
|
if file.is_symlink() && (cfg!(unix) || cfg!(windows)) {
|
||||||
let mut target = Vec::with_capacity(file.size() as usize);
|
let mut target = Vec::with_capacity(file.size() as usize);
|
||||||
file.read_to_end(&mut target)?;
|
file.read_to_end(&mut target)?;
|
||||||
|
// File no longer needed, drop to allow reading target on windows
|
||||||
|
drop(file);
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
|
|
@ -199,11 +201,15 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
let Ok(target) = String::from_utf8(target) else {
|
let Ok(target) = String::from_utf8(target) else {
|
||||||
return Err(ZipError::InvalidArchive("Invalid UTF-8 as symlink target"));
|
return Err(ZipError::InvalidArchive(
|
||||||
|
"Invalid UTF-8 as symlink target".into(),
|
||||||
|
));
|
||||||
};
|
};
|
||||||
let target = target.into_boxed_str();
|
let target_is_dir_from_archive = match password {
|
||||||
let target_is_dir_from_archive =
|
None => archive.by_name(&target),
|
||||||
archive.shared.files.contains_key(&target) && is_dir(&target);
|
Some(pwd) => archive.by_name_decrypt(&target, pwd.as_bytes()),
|
||||||
|
}
|
||||||
|
.map_or(false, |x| x.is_dir());
|
||||||
let target_path = directory.as_ref().join(OsString::from(target.to_string()));
|
let target_path = directory.as_ref().join(OsString::from(target.to_string()));
|
||||||
let target_is_dir = if target_is_dir_from_archive {
|
let target_is_dir = if target_is_dir_from_archive {
|
||||||
true
|
true
|
||||||
|
|
|
||||||
|
|
@ -441,13 +441,8 @@ impl<M: Send + 'static> Dialog<M> {
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
enum DialogPage {
|
enum DialogPage {
|
||||||
NewFolder {
|
NewFolder { parent: PathBuf, name: String },
|
||||||
parent: PathBuf,
|
Replace { filename: String },
|
||||||
name: String,
|
|
||||||
},
|
|
||||||
Replace {
|
|
||||||
filename: String,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
@ -2042,7 +2037,7 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
|
|
||||||
col = col.push(
|
col = col.push(
|
||||||
self.tab
|
self.tab
|
||||||
.view(&self.key_binds, &self.modifiers, false, &[])
|
.view(&self.key_binds, &self.modifiers, false, &[])
|
||||||
.map(Message::TabMessage),
|
.map(Message::TabMessage),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
18
src/lib.rs
18
src/lib.rs
|
|
@ -5,14 +5,18 @@ use cosmic::{app::Settings, iced::Limits};
|
||||||
use std::{env, fs, path::PathBuf, process};
|
use std::{env, fs, path::PathBuf, process};
|
||||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||||
|
|
||||||
use app::{App, Flags};
|
use crate::{
|
||||||
|
app::{App, Flags},
|
||||||
|
config::{Config, State},
|
||||||
|
tab::Location,
|
||||||
|
};
|
||||||
|
|
||||||
pub mod app;
|
pub mod app;
|
||||||
mod archive;
|
mod archive;
|
||||||
pub mod channel;
|
pub mod channel;
|
||||||
pub mod clipboard;
|
pub mod clipboard;
|
||||||
mod context_action;
|
|
||||||
use config::Config;
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
mod context_action;
|
||||||
pub mod dialog;
|
pub mod dialog;
|
||||||
mod key_bind;
|
mod key_bind;
|
||||||
pub(crate) mod large_image;
|
pub(crate) mod large_image;
|
||||||
|
|
@ -25,13 +29,11 @@ mod mounter;
|
||||||
mod mouse_area;
|
mod mouse_area;
|
||||||
pub mod operation;
|
pub mod operation;
|
||||||
mod spawn_detached;
|
mod spawn_detached;
|
||||||
use tab::Location;
|
|
||||||
mod zoom;
|
|
||||||
|
|
||||||
use crate::config::State;
|
|
||||||
pub mod tab;
|
pub mod tab;
|
||||||
mod thumbnail_cacher;
|
mod thumbnail_cacher;
|
||||||
mod thumbnailer;
|
mod thumbnailer;
|
||||||
|
pub(crate) mod trash;
|
||||||
|
mod zoom;
|
||||||
|
|
||||||
pub(crate) type FxOrderMap<K, V> = ordermap::OrderMap<K, V, rustc_hash::FxBuildHasher>;
|
pub(crate) type FxOrderMap<K, V> = ordermap::OrderMap<K, V, rustc_hash::FxBuildHasher>;
|
||||||
|
|
||||||
|
|
@ -189,7 +191,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if daemonize {
|
if daemonize {
|
||||||
#[cfg(all(unix, not(target_os = "redox")))]
|
#[cfg(all(unix, not(any(target_os = "macos", target_os = "redox"))))]
|
||||||
match fork::daemon(true, true) {
|
match fork::daemon(true, true) {
|
||||||
Ok(fork::Fork::Child) => (),
|
Ok(fork::Fork::Child) => (),
|
||||||
Ok(fork::Fork::Parent(_child_pid)) => process::exit(0),
|
Ok(fork::Fork::Parent(_child_pid)) => process::exit(0),
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ use crate::{
|
||||||
config::{Config, ContextActionPreset},
|
config::{Config, ContextActionPreset},
|
||||||
fl,
|
fl,
|
||||||
tab::{self, HeadingOptions, Location, LocationMenuAction, SearchLocation, Tab},
|
tab::{self, HeadingOptions, Location, LocationMenuAction, SearchLocation, Tab},
|
||||||
|
trash::{Trash, TrashExt},
|
||||||
};
|
};
|
||||||
|
|
||||||
static MENU_ID: LazyLock<cosmic::widget::Id> =
|
static MENU_ID: LazyLock<cosmic::widget::Id> =
|
||||||
|
|
@ -192,7 +193,7 @@ pub fn context_menu<'a>(
|
||||||
) => {
|
) => {
|
||||||
if selected_trash_only {
|
if selected_trash_only {
|
||||||
children.push(menu_item(fl!("open"), Action::Open).into());
|
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());
|
children.push(menu_item(fl!("empty-trash"), Action::EmptyTrash).into());
|
||||||
}
|
}
|
||||||
} else if let Some(entry) = selected_desktop_entry {
|
} else if let Some(entry) = selected_desktop_entry {
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ struct MimeIconKey {
|
||||||
|
|
||||||
struct MimeIconCache {
|
struct MimeIconCache {
|
||||||
cache: FxHashMap<MimeIconKey, Option<icon::Handle>>,
|
cache: FxHashMap<MimeIconKey, Option<icon::Handle>>,
|
||||||
|
#[cfg(unix)]
|
||||||
shared_mime_info: xdg_mime::SharedMimeInfo,
|
shared_mime_info: xdg_mime::SharedMimeInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -26,10 +27,17 @@ impl MimeIconCache {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
cache: FxHashMap::default(),
|
cache: FxHashMap::default(),
|
||||||
|
#[cfg(unix)]
|
||||||
shared_mime_info: xdg_mime::SharedMimeInfo::new(),
|
shared_mime_info: xdg_mime::SharedMimeInfo::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
pub fn get(&mut self, _key: MimeIconKey) -> Option<icon::Handle> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
pub fn get(&mut self, key: MimeIconKey) -> Option<icon::Handle> {
|
pub fn get(&mut self, key: MimeIconKey) -> Option<icon::Handle> {
|
||||||
self.cache
|
self.cache
|
||||||
.entry(key)
|
.entry(key)
|
||||||
|
|
@ -53,6 +61,16 @@ impl MimeIconCache {
|
||||||
static MIME_ICON_CACHE: LazyLock<Mutex<MimeIconCache>> =
|
static MIME_ICON_CACHE: LazyLock<Mutex<MimeIconCache>> =
|
||||||
LazyLock::new(|| Mutex::new(MimeIconCache::new()));
|
LazyLock::new(|| Mutex::new(MimeIconCache::new()));
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
pub fn mime_for_path(
|
||||||
|
path: impl AsRef<Path>,
|
||||||
|
metadata_opt: Option<&fs::Metadata>,
|
||||||
|
remote: bool,
|
||||||
|
) -> Mime {
|
||||||
|
mime_guess::from_path(path).first_or_octet_stream()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
pub fn mime_for_path(
|
pub fn mime_for_path(
|
||||||
path: impl AsRef<Path>,
|
path: impl AsRef<Path>,
|
||||||
metadata_opt: Option<&fs::Metadata>,
|
metadata_opt: Option<&fs::Metadata>,
|
||||||
|
|
@ -100,8 +118,13 @@ pub fn mime_icon(mime: Mime, size: u16) -> icon::Handle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
pub fn parent_mime_types(_mime: &Mime) -> Option<Vec<Mime>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
pub fn parent_mime_types(mime: &Mime) -> Option<Vec<Mime>> {
|
pub fn parent_mime_types(mime: &Mime) -> Option<Vec<Mime>> {
|
||||||
let mime_icon_cache = MIME_ICON_CACHE.lock().unwrap();
|
let mime_icon_cache = MIME_ICON_CACHE.lock().unwrap();
|
||||||
|
|
||||||
mime_icon_cache.shared_mime_info.get_parents_aliased(mime)
|
mime_icon_cache.shared_mime_info.get_parents_aliased(mime)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1125,7 +1125,9 @@ impl Operation {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
Self::Restore { .. } => {
|
Self::Restore { .. } => {
|
||||||
// TODO: add support for macos
|
// 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"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
Self::Restore { items } => {
|
Self::Restore { items } => {
|
||||||
|
|
|
||||||
199
src/tab.rs
199
src/tab.rs
|
|
@ -1,7 +1,7 @@
|
||||||
|
#[cfg(feature = "desktop")]
|
||||||
|
use cosmic::desktop::fde::{DesktopEntry, get_languages_from_env};
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
Apply, Element, cosmic_theme,
|
Apply, Element, cosmic_theme, font,
|
||||||
desktop::fde::{DesktopEntry, get_languages_from_env},
|
|
||||||
font,
|
|
||||||
iced::core::{mouse::ScrollDelta, widget::tree},
|
iced::core::{mouse::ScrollDelta, widget::tree},
|
||||||
iced::{
|
iced::{
|
||||||
Alignment, Border, Color, ContentFit, Length, Point, Rectangle, Size, Subscription, Vector,
|
Alignment, Border, Color, ContentFit, Length, Point, Rectangle, Size, Subscription, Vector,
|
||||||
|
|
@ -39,9 +39,10 @@ use icu::{
|
||||||
use image::{DynamicImage, ImageReader};
|
use image::{DynamicImage, ImageReader};
|
||||||
use jiff_icu::ConvertFrom;
|
use jiff_icu::ConvertFrom;
|
||||||
use mime_guess::{Mime, mime};
|
use mime_guess::{Mime, mime};
|
||||||
use regex::Regex;
|
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
#[cfg(unix)]
|
||||||
|
use std::os::unix::fs::MetadataExt;
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
cell::Cell,
|
cell::Cell,
|
||||||
|
|
@ -52,7 +53,6 @@ use std::{
|
||||||
fs::{self, File, Metadata},
|
fs::{self, File, Metadata},
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
io::{BufRead, BufReader},
|
io::{BufRead, BufReader},
|
||||||
os::unix::fs::MetadataExt,
|
|
||||||
path::{self, Path, PathBuf},
|
path::{self, Path, PathBuf},
|
||||||
sync::{Arc, LazyLock, RwLock, atomic},
|
sync::{Arc, LazyLock, RwLock, atomic},
|
||||||
time::{Duration, Instant, SystemTime},
|
time::{Duration, Instant, SystemTime},
|
||||||
|
|
@ -84,8 +84,8 @@ use crate::{
|
||||||
operation::{Controller, OperationError},
|
operation::{Controller, OperationError},
|
||||||
thumbnail_cacher::{CachedThumbnail, ThumbnailCacher, ThumbnailSize},
|
thumbnail_cacher::{CachedThumbnail, ThumbnailCacher, ThumbnailSize},
|
||||||
thumbnailer::thumbnailer,
|
thumbnailer::thumbnailer,
|
||||||
|
trash::{Trash, TrashExt},
|
||||||
};
|
};
|
||||||
use uzers::{get_group_by_gid, get_user_by_uid};
|
|
||||||
|
|
||||||
pub const DOUBLE_CLICK_DURATION: Duration = Duration::from_millis(500);
|
pub const DOUBLE_CLICK_DURATION: Duration = Duration::from_millis(500);
|
||||||
pub const HOVER_DURATION: Duration = Duration::from_millis(1600);
|
pub const HOVER_DURATION: Duration = Duration::from_millis(1600);
|
||||||
|
|
@ -557,6 +557,12 @@ pub fn fs_kind(_metadata: &Metadata) -> FsKind {
|
||||||
FsKind::Local
|
FsKind::Local
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "desktop"))]
|
||||||
|
fn get_desktop_file_display_name(path: &Path) -> Option<String> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "desktop")]
|
||||||
fn get_desktop_file_display_name(path: &Path) -> Option<String> {
|
fn get_desktop_file_display_name(path: &Path) -> Option<String> {
|
||||||
let locales = get_languages_from_env();
|
let locales = get_languages_from_env();
|
||||||
let entry = match DesktopEntry::from_path(path, Some(&locales)) {
|
let entry = match DesktopEntry::from_path(path, Some(&locales)) {
|
||||||
|
|
@ -570,6 +576,12 @@ fn get_desktop_file_display_name(path: &Path) -> Option<String> {
|
||||||
entry.name(&locales).map(|s| s.into_owned())
|
entry.name(&locales).map(|s| s.into_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "desktop"))]
|
||||||
|
fn get_desktop_file_icon(path: &Path) -> Option<String> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "desktop")]
|
||||||
fn get_desktop_file_icon(path: &Path) -> Option<String> {
|
fn get_desktop_file_icon(path: &Path) -> Option<String> {
|
||||||
let entry = match DesktopEntry::from_path::<&str>(path, None) {
|
let entry = match DesktopEntry::from_path::<&str>(path, None) {
|
||||||
Ok(ok) => ok,
|
Ok(ok) => ok,
|
||||||
|
|
@ -593,6 +605,7 @@ fn desktop_icon_handle(icon: &str, size: u16) -> widget::icon::Handle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "desktop")]
|
||||||
pub fn parse_desktop_file(path: &Path) -> (Option<String>, Option<String>) {
|
pub fn parse_desktop_file(path: &Path) -> (Option<String>, Option<String>) {
|
||||||
let locales = get_languages_from_env();
|
let locales = get_languages_from_env();
|
||||||
let entry = match DesktopEntry::from_path(path, Some(&locales)) {
|
let entry = match DesktopEntry::from_path(path, Some(&locales)) {
|
||||||
|
|
@ -1168,133 +1181,7 @@ pub fn scan_search<F: Fn(SearchItem) -> bool + Sync>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SearchLocation::Trash => {
|
SearchLocation::Trash => {
|
||||||
trash_helpers::scan_search_trash(callback, ®ex);
|
Trash::scan_search(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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1432,15 +1319,15 @@ pub fn scan_desktop(
|
||||||
let display_name = Item::display_name(&name);
|
let display_name = Item::display_name(&name);
|
||||||
|
|
||||||
let metadata = ItemMetadata::SimpleDir {
|
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) = {
|
let (mime, icon_handle_grid, icon_handle_list, icon_handle_list_condensed) = {
|
||||||
(
|
(
|
||||||
"inode/directory".parse().unwrap(),
|
"inode/directory".parse().unwrap(),
|
||||||
trash_helpers::trash_icon(sizes.grid()),
|
Trash::icon(sizes.grid()),
|
||||||
trash_helpers::trash_icon(sizes.list()),
|
Trash::icon(sizes.list()),
|
||||||
trash_helpers::trash_icon(sizes.list_condensed()),
|
Trash::icon(sizes.list_condensed()),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1681,7 +1568,7 @@ impl Location {
|
||||||
// Search is done incrementally
|
// Search is done incrementally
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
Self::Trash => trash_helpers::scan_trash(sizes),
|
Self::Trash => Trash::scan(sizes),
|
||||||
Self::Recents => scan_recents(sizes),
|
Self::Recents => scan_recents(sizes),
|
||||||
Self::Network(uri, _, _) => scan_network(uri, sizes),
|
Self::Network(uri, _, _) => scan_network(uri, sizes),
|
||||||
};
|
};
|
||||||
|
|
@ -2542,7 +2429,7 @@ impl Item {
|
||||||
|
|
||||||
let mode = metadata.mode();
|
let mode = metadata.mode();
|
||||||
|
|
||||||
let user_name = get_user_by_uid(metadata.uid())
|
let user_name = uzers::get_user_by_uid(metadata.uid())
|
||||||
.and_then(|user| user.name().to_str().map(ToOwned::to_owned))
|
.and_then(|user| user.name().to_str().map(ToOwned::to_owned))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let user_path = path.clone();
|
let user_path = path.clone();
|
||||||
|
|
@ -2565,7 +2452,7 @@ impl Item {
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
|
|
||||||
let group_name = get_group_by_gid(metadata.gid())
|
let group_name = uzers::get_group_by_gid(metadata.gid())
|
||||||
.and_then(|group| group.name().to_str().map(ToOwned::to_owned))
|
.and_then(|group| group.name().to_str().map(ToOwned::to_owned))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let group_path = path.clone();
|
let group_path = path.clone();
|
||||||
|
|
@ -4453,6 +4340,7 @@ impl Tab {
|
||||||
for item in self.items_opt().map_or(Vec::new(), |items| {
|
for item in self.items_opt().map_or(Vec::new(), |items| {
|
||||||
items.iter().filter(|item| item.selected).collect()
|
items.iter().filter(|item| item.selected).collect()
|
||||||
}) {
|
}) {
|
||||||
|
#[cfg(unix)]
|
||||||
if let (Some(path), Some(mode)) = (
|
if let (Some(path), Some(mode)) = (
|
||||||
item.path_opt(),
|
item.path_opt(),
|
||||||
item.file_metadata()
|
item.file_metadata()
|
||||||
|
|
@ -6470,20 +6358,23 @@ impl Tab {
|
||||||
} else {
|
} else {
|
||||||
total_size = total_size.saturating_add(metadata.len());
|
total_size = total_size.saturating_add(metadata.len());
|
||||||
}
|
}
|
||||||
let mode = metadata.mode();
|
#[cfg(unix)]
|
||||||
user_name.insert(
|
{
|
||||||
get_user_by_uid(metadata.uid())
|
let mode = metadata.mode();
|
||||||
.and_then(|user| user.name().to_str().map(ToOwned::to_owned))
|
user_name.insert(
|
||||||
.unwrap_or_default(),
|
uzers::get_user_by_uid(metadata.uid())
|
||||||
);
|
.and_then(|user| user.name().to_str().map(ToOwned::to_owned))
|
||||||
mode_user.insert(get_mode_part(mode, MODE_SHIFT_USER));
|
.unwrap_or_default(),
|
||||||
group_name.insert(
|
);
|
||||||
get_group_by_gid(metadata.gid())
|
mode_user.insert(get_mode_part(mode, MODE_SHIFT_USER));
|
||||||
.and_then(|group| group.name().to_str().map(ToOwned::to_owned))
|
group_name.insert(
|
||||||
.unwrap_or_default(),
|
uzers::get_group_by_gid(metadata.gid())
|
||||||
);
|
.and_then(|group| group.name().to_str().map(ToOwned::to_owned))
|
||||||
mode_group.insert(get_mode_part(mode, MODE_SHIFT_GROUP));
|
.unwrap_or_default(),
|
||||||
mode_other.insert(get_mode_part(mode, MODE_SHIFT_OTHER));
|
);
|
||||||
|
mode_group.insert(get_mode_part(mode, MODE_SHIFT_GROUP));
|
||||||
|
mode_other.insert(get_mode_part(mode, MODE_SHIFT_OTHER));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut mime_types: Vec<(String, u64)> = mime_type_counts.into_iter().collect();
|
let mut mime_types: Vec<(String, u64)> = mime_type_counts.into_iter().collect();
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
use image::DynamicImage;
|
use image::DynamicImage;
|
||||||
use md5::{Digest, Md5};
|
use md5::{Digest, Md5};
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
|
#[cfg(unix)]
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
use std::{
|
use std::{
|
||||||
error::Error,
|
error::Error,
|
||||||
fs::{self, File},
|
fs::{self, File},
|
||||||
io::{self, BufReader, BufWriter},
|
io::{self, BufReader, BufWriter},
|
||||||
os::unix::fs::PermissionsExt,
|
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::LazyLock,
|
sync::LazyLock,
|
||||||
time::UNIX_EPOCH,
|
time::UNIX_EPOCH,
|
||||||
|
|
@ -93,6 +94,7 @@ impl ThumbnailCacher {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_with_temp_file(&self, temp_file: NamedTempFile) -> Result<&Path, Box<dyn Error>> {
|
pub fn update_with_temp_file(&self, temp_file: NamedTempFile) -> Result<&Path, Box<dyn Error>> {
|
||||||
|
#[cfg(unix)]
|
||||||
fs::set_permissions(temp_file.path(), fs::Permissions::from_mode(0o600))?;
|
fs::set_permissions(temp_file.path(), fs::Permissions::from_mode(0o600))?;
|
||||||
self.update_thumbnail_text_metadata(temp_file.path())?;
|
self.update_thumbnail_text_metadata(temp_file.path())?;
|
||||||
fs::rename(temp_file.path(), &self.thumbnail_path)?;
|
fs::rename(temp_file.path(), &self.thumbnail_path)?;
|
||||||
|
|
@ -127,6 +129,7 @@ impl ThumbnailCacher {
|
||||||
pub fn create_fail_marker(&self) -> Result<(), Box<dyn Error>> {
|
pub fn create_fail_marker(&self) -> Result<(), Box<dyn Error>> {
|
||||||
if let Some(dir) = self.thumbnail_fail_marker_path.parent() {
|
if let Some(dir) = self.thumbnail_fail_marker_path.parent() {
|
||||||
fs::create_dir_all(dir)?;
|
fs::create_dir_all(dir)?;
|
||||||
|
#[cfg(unix)]
|
||||||
fs::set_permissions(dir, fs::Permissions::from_mode(0o700))?;
|
fs::set_permissions(dir, fs::Permissions::from_mode(0o700))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright 2023 System76 <info@system76.com>
|
// Copyright 2023 System76 <info@system76.com>
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
#[cfg(feature = "desktop")]
|
||||||
use cosmic::desktop::fde::GenericEntry;
|
use cosmic::desktop::fde::GenericEntry;
|
||||||
use mime_guess::Mime;
|
use mime_guess::Mime;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
|
|
|
||||||
145
src/trash.rs
Normal file
145
src/trash.rs
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not available on Windows only
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
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