use std::{fs, io::stdin, path::PathBuf};
use anyhow::{Context, Result};
use crate::fuzzy::FuzzyFinderItem;
use super::{args::*, bail, debug, fsutils::*, items::*};
pub fn list(action: ListTrashItems, config: &Config) -> Result<()> {
let ListTrashItems { name } = action;
debug!("Listing trash items...");
let mut items = list_all_trash_items(config)?;
if items.is_empty() {
println!("All trashes are empty.");
return Ok(());
}
if let Some(name) = &name {
debug!("Filtering {} items by name...", items.len());
items.retain(|trashed| trashed.data.filename().contains(name));
if items.is_empty() {
println!("No item in trash match the provided name.");
return Ok(());
}
}
println!("{}", table_for_items(&items));
Ok(())
}
pub fn remove(action: MoveToTrash, config: &Config) -> Result<()> {
let MoveToTrash {
paths,
permanently,
ignore,
allow_invalid_utf8_item_names,
} = action;
debug!("Going to remove {} item(s)...", paths.len());
for (i, path) in paths.iter().enumerate() {
debug!("Treating item {} on {}...", i + 1, paths.len());
let path = PathBuf::from(path);
debug!("Checking if item exists...");
if is_dangerous_path(&path) {
bail!("Removing this path is too dangerous, operation aborted.");
}
if !path.exists() {
if ignore {
continue;
}
bail!("Item path does not exist.");
}
if permanently {
let deletion_result = if path.is_file() {
fs::remove_file(&path)
} else {
fs::remove_dir_all(&path)
};
match deletion_result {
Err(err) => bail!("Failed to permanently remove item: {}", err),
Ok(()) => continue,
}
}
let filename = path
.file_name()
.context("Specified item path has no file name")?;
let filename = match filename.to_str() {
Some(str) => str.to_string(),
None => {
if allow_invalid_utf8_item_names {
filename.to_string_lossy().to_string()
} else {
bail!("Specified item does not have a valid UTF-8 file name")
}
}
};
let data = TrashItemInfos::new_now(filename.to_string());
debug!(
"Moving item to trash under name '{}'...",
data.trash_filename()
);
let trash_dir = determine_trash_dir_for(&path, config).with_context(|| {
format!(
"Failed to determine path to the trash directory for item: {}",
path.display()
)
})?;
if !trash_dir.exists() {
fs::create_dir(&trash_dir).with_context(|| {
format!(
"Failed to create trash directory at path '{}'",
trash_dir.display()
)
})?;
}
let trash_transfer_dir = trash_dir.join(TRASH_TRANSFER_DIRNAME);
if !trash_transfer_dir.exists() {
fs::create_dir(&trash_transfer_dir).with_context(|| {
format!(
"Failed to create trash's partial transfer directory at path '{}'",
trash_transfer_dir.display()
)
})?;
}
if !are_on_same_fs(&path, &trash_dir)? {
println!("Moving item to trash directory {}", trash_dir.display());
let transfer_path = trash_transfer_dir.join(data.trash_filename());
move_item_pbr(&path, &transfer_path).context("Failed to move item to the trash")?;
fs::rename(&transfer_path, trash_dir.join(data.trash_filename()))
.context("Failed to move item to the final trash directory")?;
} else {
let trash_item = TrashedItem { data, trash_dir };
let trash_item_path = trash_item.transfer_trash_item_path();
fs::rename(&path, &trash_item_path)
.with_context(|| format!("Failed to move item '{}' to trash", path.display()))?;
fs::rename(&trash_item_path, trash_item.complete_trash_item_path()).with_context(
|| {
format!(
"Failed to move fully transferred item '{}' to trash",
path.display()
)
},
)?;
}
}
Ok(())
}
pub fn drop(action: DropItem, config: &Config) -> Result<()> {
let DropItem { filename, id } = action;
debug!("Listing trash items...");
let item = expect_single_trash_item(&filename, id.as_deref(), config)?;
debug!("Permanently removing item from trash...");
let path = item.complete_trash_item_path();
let result = if path.is_dir() {
fs::remove_dir_all(path)
} else {
fs::remove_file(path)
};
result.with_context(|| {
format!(
"Failed to remove item '{}' from trash",
item.data.filename()
)
})
}
pub fn path_of(action: GetItemPath, config: &Config) -> Result<()> {
let GetItemPath {
filename,
id,
allow_invalid_utf8_path,
} = action;
debug!("Listing trash items...");
let item = expect_single_trash_item(&filename, id.as_deref(), config)?;
let item_path = item.complete_trash_item_path();
match item_path.to_str() {
Some(path) => println!("{}", path),
None => {
if allow_invalid_utf8_path {
println!("{}", item_path.to_string_lossy())
} else {
bail!(
"Path contains invalid UTF-8 characters (lossy: {})",
item_path.to_string_lossy()
);
}
}
}
Ok(())
}
pub fn restore(action: RestoreItem, config: &Config) -> Result<()> {
let RestoreItem { filename, to, id } = action;
debug!("Listing trash items...");
let Some(filename) = filename else {
return restore_with_ui(config);
};
let item = expect_single_trash_item(&filename, id.as_deref(), config)?;
let item_path = item.complete_trash_item_path();
let target_path = match to {
Some(to) => to,
None => std::env::current_dir()?,
};
let target_path = target_path.join(item.data.filename());
if target_path.exists() {
bail!("Target path already exists.");
}
let target_parent = target_path.parent().unwrap();
if !target_parent.exists() {
bail!(
"Target directory '{}' does not exist",
target_parent.display()
);
}
let result = if are_on_same_fs(&item.complete_trash_item_path(), target_parent)? {
debug!("Restoring item from trash...");
fs::rename(item_path, &target_path).context("Rename operation failed")
} else {
println!("Moving file across filesystems...");
move_item_pbr(&item_path, &target_path)
};
result.with_context(|| {
format!(
"Failed to restore item '{}' from trash",
item.data.filename()
)
})
}
pub fn restore_with_ui(config: &Config) -> Result<()> {
let items = list_all_trash_items(config)?;
if items.is_empty() {
println!("Trash is empty");
return Ok(());
}
let to_remove = crate::fuzzy::run_fuzzy_finder(
items
.into_iter()
.map(|item| FuzzyFinderItem {
display: format!(
"[{}] {}",
item.data.datetime().to_rfc2822(),
item.data.filename()
),
value: item,
})
.collect(),
)?;
restore(
RestoreItem {
filename: Some(to_remove.data.filename().to_owned()),
to: None,
id: Some(to_remove.data.id().to_owned()),
},
config,
)?;
Ok(())
}
pub fn empty(config: &Config) -> Result<()> {
let trash_dirs = list_trash_dirs(config)?;
let items = list_all_trash_items(config)?;
println!("You are about to delete the entire trash directories of:\n");
for trash_dir in &trash_dirs {
println!(
" {} ({} items)",
trash_dir.display(),
items
.iter()
.filter(|item| &item.trash_dir == trash_dir)
.count()
);
}
println!("\nAre you sure you want to continue [Y/N]?");
let mut confirm_str = String::new();
stdin()
.read_line(&mut confirm_str)
.context("Failed to get user confirmation")?;
if confirm_str.trim().to_ascii_lowercase() != "y" {
println!("Cancelled.");
return Ok(());
}
println!("Emptying the trash...");
let mut failed = false;
for trash_dir in trash_dirs {
println!("Emptying trash directory: {}", trash_dir.display());
if let Err(err) = fs::remove_dir_all(&trash_dir) {
println!(
"Failed to empty the trash at path: {}\n\n{err}",
trash_dir.display()
);
failed = true;
}
}
if failed {
bail!("Failed to empty trash directories");
}
println!("Trash was successfully emptied.");
Ok(())
}
pub fn trash_path(config: &Config) -> Result<()> {
let current_dir =
std::env::current_dir().context("Failed to determine path to the current directory")?;
let trash_dir = determine_trash_dir_for(¤t_dir, config)?;
println!("{}", trash_dir.display());
Ok(())
}