Skip to content

Commit 090d1b5

Browse files
committed
Improve directories exclusion computation
1 parent cfcc696 commit 090d1b5

File tree

6 files changed

+88
-87
lines changed

6 files changed

+88
-87
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "trasher"
3-
version = "4.1.0"
3+
version = "4.2.0"
44
authors = ["Clément Nerma <clement.nerma@gmail.com>"]
55
edition = "2021"
66
license = "Apache-2.0"

src/actions.rs

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ use crate::fuzzy::FuzzyFinderItem;
99

1010
use super::{args::*, bail, fsutils::*, items::*};
1111

12-
pub fn list(action: ListTrashItems, config: &Config) -> Result<()> {
12+
pub fn list(action: ListTrashItems, exclude_dirs: &[PathBuf]) -> Result<()> {
1313
let ListTrashItems { name } = action;
1414

1515
debug!("Listing trash items...");
1616

17-
let mut items = list_all_trash_items(config)?;
17+
let mut items = list_all_trash_items(exclude_dirs)?;
1818

1919
if items.is_empty() {
2020
info!("All trashes are empty.");
@@ -36,7 +36,7 @@ pub fn list(action: ListTrashItems, config: &Config) -> Result<()> {
3636
Ok(())
3737
}
3838

39-
pub fn remove(action: MoveToTrash, config: &Config) -> Result<()> {
39+
pub fn remove(action: MoveToTrash, exclude_dirs: &[PathBuf]) -> Result<()> {
4040
let MoveToTrash {
4141
paths,
4242
permanently,
@@ -100,7 +100,7 @@ pub fn remove(action: MoveToTrash, config: &Config) -> Result<()> {
100100
data.trash_filename()
101101
);
102102

103-
let trash_dir = determine_trash_dir_for(&path, config).with_context(|| {
103+
let trash_dir = determine_trash_dir_for(&path, exclude_dirs).with_context(|| {
104104
format!(
105105
"Failed to determine path to the trash directory for item: {}",
106106
path.display()
@@ -157,12 +157,12 @@ pub fn remove(action: MoveToTrash, config: &Config) -> Result<()> {
157157
Ok(())
158158
}
159159

160-
pub fn drop(action: DropItem, config: &Config) -> Result<()> {
160+
pub fn drop(action: DropItem, exclude_dirs: &[PathBuf]) -> Result<()> {
161161
let DropItem { filename, id } = action;
162162

163163
debug!("Listing trash items...");
164164

165-
let item = expect_single_trash_item(&filename, id.as_deref(), config)?;
165+
let item = expect_single_trash_item(&filename, id.as_deref(), exclude_dirs)?;
166166

167167
debug!("Permanently removing item from trash...");
168168

@@ -177,7 +177,7 @@ pub fn drop(action: DropItem, config: &Config) -> Result<()> {
177177
result.with_context(|| format!("Failed to remove item '{}' from trash", item.data.filename))
178178
}
179179

180-
pub fn path_of(action: GetItemPath, config: &Config) -> Result<()> {
180+
pub fn path_of(action: GetItemPath, exclude_dirs: &[PathBuf]) -> Result<()> {
181181
let GetItemPath {
182182
filename,
183183
id,
@@ -186,7 +186,7 @@ pub fn path_of(action: GetItemPath, config: &Config) -> Result<()> {
186186

187187
debug!("Listing trash items...");
188188

189-
let item = expect_single_trash_item(&filename, id.as_deref(), config)?;
189+
let item = expect_single_trash_item(&filename, id.as_deref(), exclude_dirs)?;
190190
let item_path = item.complete_trash_item_path();
191191

192192
match item_path.to_str() {
@@ -206,16 +206,16 @@ pub fn path_of(action: GetItemPath, config: &Config) -> Result<()> {
206206
Ok(())
207207
}
208208

209-
pub fn restore(action: RestoreItem, config: &Config) -> Result<()> {
209+
pub fn restore(action: RestoreItem, exclude_dirs: &[PathBuf]) -> Result<()> {
210210
let RestoreItem { filename, to, id } = action;
211211

212212
debug!("Listing trash items...");
213213

214214
let Some(filename) = filename else {
215-
return restore_with_ui(config);
215+
return restore_with_ui(exclude_dirs);
216216
};
217217

218-
let item = expect_single_trash_item(&filename, id.as_deref(), config)?;
218+
let item = expect_single_trash_item(&filename, id.as_deref(), exclude_dirs)?;
219219

220220
let item_path = item.complete_trash_item_path();
221221

@@ -252,8 +252,8 @@ pub fn restore(action: RestoreItem, config: &Config) -> Result<()> {
252252
result.with_context(|| format!("Failed to restore item '{}' from trash", item.data.filename))
253253
}
254254

255-
pub fn restore_with_ui(config: &Config) -> Result<()> {
256-
let items = list_all_trash_items(config)?;
255+
pub fn restore_with_ui(exclude_dirs: &[PathBuf]) -> Result<()> {
256+
let items = list_all_trash_items(exclude_dirs)?;
257257

258258
if items.is_empty() {
259259
info!("Trash is empty");
@@ -282,15 +282,15 @@ pub fn restore_with_ui(config: &Config) -> Result<()> {
282282
to: None,
283283
id: Some(to_remove.data.compute_id().to_owned()),
284284
},
285-
config,
285+
exclude_dirs,
286286
)?;
287287

288288
Ok(())
289289
}
290290

291-
pub fn empty(config: &Config) -> Result<()> {
292-
let trash_dirs = list_trash_dirs(config)?;
293-
let items = list_all_trash_items(config)?;
291+
pub fn empty(exclude_dirs: &[PathBuf]) -> Result<()> {
292+
let trash_dirs = list_trash_dirs(exclude_dirs)?;
293+
let items = list_all_trash_items(exclude_dirs)?;
294294

295295
if items.is_empty() {
296296
info!("Trash is empty");
@@ -368,11 +368,11 @@ pub fn empty(config: &Config) -> Result<()> {
368368
Ok(())
369369
}
370370

371-
pub fn trash_path(config: &Config) -> Result<()> {
371+
pub fn trash_path(exclude_dirs: &[PathBuf]) -> Result<()> {
372372
let current_dir =
373373
std::env::current_dir().context("Failed to determine path to the current directory")?;
374374

375-
let trash_dir = determine_trash_dir_for(&current_dir, config)?;
375+
let trash_dir = determine_trash_dir_for(&current_dir, exclude_dirs)?;
376376

377377
println!("{}", trash_dir.display());
378378

src/args.rs

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
use std::path::PathBuf;
22

3-
use clap::{Args, Parser, Subcommand};
3+
use clap::{Parser, Subcommand};
44
use log::LevelFilter;
55

66
#[derive(Parser)]
77
#[clap(author, version, about, long_about = None)]
8-
pub struct CmdArgs {
8+
pub struct Args {
99
#[clap(
1010
short,
1111
long,
@@ -15,21 +15,16 @@ pub struct CmdArgs {
1515
)]
1616
pub verbosity: LevelFilter,
1717

18-
#[clap(subcommand)]
19-
pub action: Action,
20-
21-
#[clap(flatten)]
22-
pub config: Config,
23-
}
24-
25-
#[derive(Args)]
26-
pub struct Config {
2718
#[clap(
2819
global = true,
20+
short,
2921
long,
3022
help = "Disallow making a filesystem-local trash directory in some paths"
3123
)]
3224
pub exclude: Vec<PathBuf>,
25+
26+
#[clap(subcommand)]
27+
pub action: Action,
3328
}
3429

3530
#[derive(Subcommand)]

src/fsutils.rs

Lines changed: 44 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ use std::{
33
collections::BTreeSet,
44
ffi::OsStr,
55
fs,
6-
path::Component,
7-
path::{Path, PathBuf},
6+
path::{Component, Path, PathBuf},
87
rc::Rc,
98
};
109

@@ -17,8 +16,6 @@ use log::{debug, error, warn};
1716
use mountpoints::mountpaths;
1817
use walkdir::WalkDir;
1918

20-
use crate::Config;
21-
2219
use super::items::TrashItemInfos;
2320

2421
/// Name of the trash directory
@@ -42,26 +39,20 @@ pub static ALWAYS_EXCLUDE_DIRS: &[&str] = &[
4239
"/var/lib/docker",
4340
];
4441

45-
/// Determine path to the trash directory for a given item and create it if required
46-
pub fn determine_trash_dir_for(item: &Path, config: &Config) -> Result<PathBuf> {
47-
debug!("Determining trasher directory for item: {}", item.display());
48-
49-
let home_dir = dirs::home_dir().context("Failed to determine path to user's home directory")?;
42+
/// Compute the list of directories to exclude
43+
pub fn compute_exclusions(exclude_dirs: &[PathBuf]) -> Result<Vec<PathBuf>> {
44+
debug!("Computing directories to exclude...");
5045

51-
let mut exclude = config
52-
.exclude
46+
let mut exclude = exclude_dirs
5347
.iter()
54-
.filter_map(|dir| {
55-
if !dir.is_dir() {
56-
None
57-
} else {
58-
Some(fs::canonicalize(dir).with_context(|| {
59-
format!(
60-
"Failed to canonicalize excluded directory: {}",
61-
dir.display()
62-
)
63-
}))
64-
}
48+
.filter(|dir| dir.is_dir())
49+
.map(|dir| {
50+
fs::canonicalize(dir).with_context(|| {
51+
format!(
52+
"Failed to canonicalize excluded directory: {}",
53+
dir.display()
54+
)
55+
})
6556
})
6657
.collect::<Result<Vec<_>, _>>()?;
6758

@@ -73,14 +64,23 @@ pub fn determine_trash_dir_for(item: &Path, config: &Config) -> Result<PathBuf>
7364
.map(Path::to_owned),
7465
);
7566

67+
Ok(exclude)
68+
}
69+
70+
/// Determine path to the trash directory for a given item and create it if required
71+
pub fn determine_trash_dir_for(item: &Path, exclude_dirs: &[PathBuf]) -> Result<PathBuf> {
72+
debug!("Determining trasher directory for item: {}", item.display());
73+
74+
let home_dir = dirs::home_dir().context("Failed to determine path to user's home directory")?;
75+
7676
// Don't canonicalize excluded item paths
7777
// NOTE: Only works if item path is absolute
78-
if exclude.iter().any(|dir| item.starts_with(dir)) {
78+
if exclude_dirs.iter().any(|dir| item.starts_with(dir)) {
7979
return Ok(home_dir.join(TRASH_DIR_NAME));
8080
}
8181

8282
let item = fs::canonicalize(item)
83-
.with_context(|| format!("Failed to canonicalize item path: {}\n\nTip: you can exclude this directory using --exclude.", item.display()))?;
83+
.with_context(|| format!("Failed to canonicalize item path: {}", item.display()))?;
8484

8585
let mut mountpoints = mountpaths().context("Failed to list system mountpoints")?;
8686

@@ -125,7 +125,7 @@ pub fn determine_trash_dir_for(item: &Path, config: &Config) -> Result<PathBuf>
125125
continue;
126126
}
127127

128-
if exclude.iter().any(|parent| item.starts_with(parent)) {
128+
if exclude_dirs.iter().any(|parent| item.starts_with(parent)) {
129129
found = None;
130130
break;
131131
}
@@ -140,25 +140,28 @@ pub fn determine_trash_dir_for(item: &Path, config: &Config) -> Result<PathBuf>
140140
}
141141

142142
/// List all trash directories
143-
pub fn list_trash_dirs(config: &Config) -> Result<BTreeSet<PathBuf>> {
143+
pub fn list_trash_dirs(exclude_dirs: &[PathBuf]) -> Result<BTreeSet<PathBuf>> {
144144
let canon_root = fs::canonicalize("/").context("Failed to canonicalize the root directory")?;
145145

146146
let trash_dirs = mountpaths()
147147
.context("Failed to list system mountpoints")?
148148
.iter()
149149
.chain([canon_root].iter())
150-
.filter(|path| match fs::metadata(path) {
151-
Ok(_) => true,
152-
153-
Err(err) => {
154-
warn!("Warning: Skipping mountpoint {}: {err}", path.display());
155-
false
150+
.filter(|dir| {
151+
!exclude_dirs
152+
.iter()
153+
.any(|excluded| dir.starts_with(excluded))
154+
})
155+
.filter_map(|dir| match fs::metadata(dir) {
156+
Ok(_) => Some(dir.join(TRASH_DIR_NAME)),
157+
Err(_) => {
158+
warn!("Skipping unavailable directory: {}", dir.display());
159+
None
156160
}
157161
})
158-
.map(|path| determine_trash_dir_for(path, config))
159-
.collect::<Result<Vec<_>, _>>()?;
162+
.collect();
160163

161-
Ok(trash_dirs.into_iter().filter(|dir| dir.is_dir()).collect())
164+
Ok(trash_dirs)
162165
}
163166

164167
/// List and parse all items in the trash
@@ -213,8 +216,8 @@ pub fn list_trash_items(trash_dir: &Path) -> Result<Vec<TrashedItem>> {
213216
}
214217

215218
/// List all trash items
216-
pub fn list_all_trash_items(config: &Config) -> Result<Vec<TrashedItem>> {
217-
let all_trash_items = list_trash_dirs(config)?
219+
pub fn list_all_trash_items(exclude_dirs: &[PathBuf]) -> Result<Vec<TrashedItem>> {
220+
let all_trash_items = list_trash_dirs(exclude_dirs)?
218221
.into_iter()
219222
.map(|trash_dir| list_trash_items(&trash_dir))
220223
.collect::<Result<Vec<_>, _>>()?;
@@ -229,9 +232,9 @@ pub fn list_all_trash_items(config: &Config) -> Result<Vec<TrashedItem>> {
229232
pub fn expect_trash_item(
230233
filename: &str,
231234
id: Option<&str>,
232-
config: &Config,
235+
exclude_dirs: &[PathBuf],
233236
) -> Result<FoundTrashItems> {
234-
let mut candidates = list_all_trash_items(config)?
237+
let mut candidates = list_all_trash_items(exclude_dirs)?
235238
.into_iter()
236239
.filter(|trashed| trashed.data.filename == filename)
237240
.collect::<Vec<_>>();
@@ -257,9 +260,9 @@ pub fn expect_trash_item(
257260
pub fn expect_single_trash_item(
258261
filename: &str,
259262
id: Option<&str>,
260-
config: &Config,
263+
exclude_dirs: &[PathBuf],
261264
) -> Result<TrashedItem> {
262-
match expect_trash_item(filename, id, config)? {
265+
match expect_trash_item(filename, id, exclude_dirs)? {
263266
FoundTrashItems::Single(item) => Ok(item),
264267
FoundTrashItems::Multi(candidates) => bail!(
265268
"Multiple items with this filename were found in the trash:\n\n{}",

0 commit comments

Comments
 (0)