use chrono::{prelude::*, Duration}; use clap::{Parser, Subcommand}; use std::path::PathBuf; /// A simple trash utility #[derive(Parser)] #[command(name = "trash")] #[command(about = "A simple trash utility", long_about = None)] struct Cli { #[clap(subcommand)] command: Commands, } #[derive(Subcommand)] enum Commands { /// Lists trashed files List { path: Option<PathBuf> }, /// Put a file into the trash Put { path: Vec<PathBuf> }, /// Restore a file by its id Restore { id: PathBuf }, /// Prune files older than n days (defaults to 7) Prune { age: Option<u64> }, } fn list<P: Fn(&trash::TrashItem) -> bool>(predicate: P) -> Vec<trash::TrashItem> { let trashed = trash::os_limited::list().unwrap(); trashed.into_iter().filter(predicate).collect() } fn print_list(path: Option<PathBuf>) { let predicate: Box<dyn Fn(&trash::TrashItem) -> bool> = match &path { Some(p) => Box::new(|item| { let orig = match item.original_parent.canonicalize() { Ok(p) => p, Err(_) => item.original_parent.clone(), }; let pc = p.canonicalize(); match pc { Ok(pc) => pc == orig, Err(_) => false, } }), None => Box::new(|_| true), }; for item in list(predicate.as_ref()) { println!( "{}\0{}\0{}", item.original_path().display(), item.id.into_string().unwrap(), Local .timestamp_opt(item.time_deleted, 0) .unwrap() .format("%Y-%m-%d %H:%M:%S"), ); } } fn put(path: Vec<PathBuf>) { trash::delete_all(path).unwrap() } fn restore(id: PathBuf) { let items: Vec<trash::TrashItem> = list(|_| true).into_iter().filter(|i| i.id == id).collect(); match items.len() { 0 => panic!("No trashed file with that id"), 1 => (), _ => panic!("Multiple trashed files with that id"), } trash::os_limited::restore_all(items).unwrap() } fn prune(max_days: Option<u64>) { let cutoff = { Utc::now() - Duration::days(max_days.unwrap_or(7) as i64) }; let predicate = |item: &trash::TrashItem| Utc.timestamp_opt(item.time_deleted, 0).unwrap() < cutoff; let items: Vec<trash::TrashItem> = list(predicate).into_iter().collect(); trash::os_limited::purge_all(items).unwrap() } fn main() { let args = Cli::parse(); match args.command { Commands::List { path } => print_list(path), Commands::Put { path } => put(path), Commands::Restore { id } => restore(id), Commands::Prune { age } => prune(age), } }