feat: view folder's content in trash

- [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:
ZlordHUN 2026-06-29 18:39:12 +02:00 • committed by GitHub
parent 50deb28a11
commit 49107187a4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 154 additions and 29 deletions

View file

@ -1,11 +1,30 @@
use cosmic::widget;
use regex::Regex;
use std::collections::HashSet;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use crate::config::IconSizes;
use crate::tab::{Item, SearchItem};
fn percent_decode(s: &str) -> Option<String> {
let (mut r, mut b) = (String::with_capacity(s.len()), s.bytes());
while let Some(c) = b.next() {
if c == b'%' {
let (hi, lo) = (b.next()?, b.next()?);
let h = |x: u8| match x {
b'0'..=b'9' => Some(x - b'0'),
b'a'..=b'f' => Some(x - b'a' + 10),
b'A'..=b'F' => Some(x - b'A' + 10),
_ => None,
};
r.push((h(hi)? << 4 | h(lo)?) as char);
} else {
r.push(c as char);
}
}
Some(r)
}
pub trait TrashExt {
fn is_empty() -> bool {
true
@ -49,6 +68,46 @@ pub trait TrashExt {
}
}
/// Derive the actual filesystem path of a trashed item from its .trashinfo path
/// (or return the id directly if it's already a filesystem path).
pub fn trash_item_path(item: &trash::TrashItem) -> Option<PathBuf> {
let id_path = Path::new(&item.id);
if id_path.extension().map_or(false, |e| e == "trashinfo") {
let trash_root = id_path.parent()?.parent()?;
let file_name = id_path.file_stem()?;
Some(trash_root.join("files").join(file_name))
} else {
Some(PathBuf::from(&item.id))
}
}
/// For a path inside a trash `files/` directory, reconstruct the original path
/// by reading the parent `.trashinfo` file.
///
/// Given `~/.local/share/Trash/files/folder/sub/file.txt`:
/// - The top-level trashed item is `folder`
/// - 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 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 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);
if !rel.as_os_str().is_empty() {
result.push(rel);
}
Some(result)
}
/// 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"))
}
pub struct Trash;
// This config statement is from trash::os_limited