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:
parent
50deb28a11
commit
49107187a4
6 changed files with 154 additions and 29 deletions
61
src/trash.rs
61
src/trash.rs
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue