fix: not all files directory are trash (#1933)

At the moment any directory anywhere named "files" is considered trash
by cosmic-files and shown as empty.
This tightens the check on what is a trash directory with a fallback
where it also expect an info directory next to the files directory(only
used if the system doesn't report any trash directory).

As an added bonus I fixed the let _ = THUMB_SEMAPHORE.acquire() that was
immediately discarded and thus dead code.

- [X] I have disclosed use of any AI generated code in my commit
messages.
- If you are using an LLM, and do not fully understand the changes it is
making to the code base, do not create a PR.
- In our experience, AI generated code often results in overly complex
code that lacks enough context for a proper fix or feature inclusion.
This results in considerably longer code reviews. Due to this, AI
authored or partially authored PRs may be closed without comment.
- [X] I understand these changes in full and will be able to respond to
review comments.
- [X] My change is accurately described in the commit message.
- [X] My contribution is tested and working as described.
- [X] I have read the [Developer Certificate of
Origin](https://developercertificate.org/) and certify my contribution
under its conditions.
This commit is contained in:
Jeremy Soller 2026-07-21 06:55:26 -06:00 • committed by GitHub
commit 9a21542825
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 44 additions and 8 deletions

View file

@ -939,11 +939,24 @@ fn item_from_trash_child(
metadata: fs::Metadata,
sizes: IconSizes,
) -> Option<Item> {
let original_path = crate::trash::original_path_for_trash_child(&path)?;
let Some(original_path) = crate::trash::original_path_for_trash_child(&path) else {
log::warn!(
"failed to resolve original path for trash item {}, skipping entry",
path.display()
);
return None;
};
let Some(original_parent) = original_path.parent() else {
log::warn!(
"trash item {} has no original parent, skipping entry",
path.display()
);
return None;
};
let entry = trash::TrashItem {
id: path.as_os_str().to_os_string(),
name: std::ffi::OsString::from(&name),
original_parent: original_path.parent()?.to_path_buf(),
original_parent: original_parent.to_path_buf(),
time_deleted: 0,
};
let size = if metadata.is_dir() {
@ -1033,6 +1046,7 @@ pub fn scan_path(tab_path: &PathBuf, sizes: IconSizes) -> Vec<Item> {
if !remote_scannable {
match fs::read_dir(tab_path) {
Ok(entries) => {
let trash = crate::trash::is_trash_path(tab_path);
items = entries
.filter_map(|entry_res| {
let entry = entry_res
@ -1073,7 +1087,6 @@ pub fn scan_path(tab_path: &PathBuf, sizes: IconSizes) -> Vec<Item> {
})
.ok()?;
let trash = crate::trash::is_trash_path(tab_path);
if trash {
item_from_trash_child(path, name, metadata, sizes)
} else {
@ -7003,7 +7016,8 @@ impl Tab {
let path = path.clone();
// Acquire semaphore permit
_ = THUMB_SEMAPHORE.acquire().await;
let _permit =
THUMB_SEMAPHORE.acquire().await.unwrap();
tokio::task::spawn_blocking(move || {
let start = Instant::now();

View file

@ -2,6 +2,7 @@ use cosmic::widget;
use regex::Regex;
use std::collections::HashSet;
use std::path::{Path, PathBuf};
use std::sync::LazyLock;
use crate::config::IconSizes;
use crate::tab::{Item, SearchItem};
@ -89,11 +90,12 @@ pub fn trash_item_path(item: &trash::TrashItem) -> Option<PathBuf> {
/// - Read `~/.local/share/Trash/info/folder.trashinfo` to get the original path
/// - Compute: `<original_path>/sub/file.txt`
pub fn original_path_for_trash_child(p: &Path) -> Option<PathBuf> {
let files = p.ancestors().find(|a| a.ends_with("files"))?;
let files = trash_files_dir(p)?;
let root = files.parent()?;
let top = p.strip_prefix(files).ok()?.components().next()?;
let info =
std::fs::read_to_string(root.join("info").join(top).with_extension("trashinfo")).ok()?;
let mut trashinfo_name = top.as_os_str().to_os_string();
trashinfo_name.push(".trashinfo");
let info = std::fs::read_to_string(root.join("info").join(trashinfo_name)).ok()?;
let orig = percent_decode(info.lines().find_map(|l| l.strip_prefix("Path="))?.trim())?;
let rel = p.strip_prefix(files.join(top)).ok()?;
let mut result = PathBuf::from(&orig);
@ -103,9 +105,29 @@ pub fn original_path_for_trash_child(p: &Path) -> Option<PathBuf> {
Some(result)
}
static TRASH_FOLDERS: LazyLock<HashSet<PathBuf>> = LazyLock::new(|| {
Trash::folders().unwrap_or_else(|e| {
log::warn!("failed to list trash folders: {}", e);
HashSet::new()
})
});
fn is_trash_root(root: &Path) -> bool {
if TRASH_FOLDERS.is_empty() {
root.join("info").is_dir()
} else {
TRASH_FOLDERS.contains(root)
}
}
fn trash_files_dir(path: &Path) -> Option<&Path> {
path.ancestors()
.find(|a| a.ends_with("files") && a.parent().is_some_and(is_trash_root))
}
/// Check whether a path is inside any trash `files/` directory.
pub fn is_trash_path(path: &Path) -> bool {
path.ancestors().any(|a| a.ends_with("files"))
trash_files_dir(path).is_some()
}
pub struct Trash;