diff --git a/CHANGELOG.md b/CHANGELOG.md index 012085f..9424d92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.1.12] - 2025-09-10 + +### Changed +- **Improved variable naming**: Updated variable names to be more descriptive and consistent across the codebase. + +### Fixed + +- **Conflicting trash items**: Updated logic to handle conflicts when recovering files with the same name. + Now, if a file with the same name exists in the original location, the recovered file will be renamed with a timestamp suffix to avoid overwriting. + + ## [0.1.11] - 2025-09-07 ### Changed @@ -17,11 +28,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.1.10] - 2025-09-07 ### Fixed + - **Critical ignore flag bug**: Fixed issue where `-i, --ignore` flag was not functioning correctly, causing errors while trying to ingore them while using `rmxt` commands ### Changed -- Imporved different cases handling in the ignore flag logic with match block +- Imporved different cases handling in the ignore flag logic with match block ## [0.1.9] - 2025-09-01 diff --git a/src/args.rs b/src/args.rs index 19262a7..4c50fb2 100644 --- a/src/args.rs +++ b/src/args.rs @@ -11,7 +11,7 @@ pub struct Args { #[command(subcommand)] pub command: Option, - /// Don't put the file in trash, remove it permanently + /// Don't put the item in trash, remove it permanently #[arg(short = 'p', long, global = false)] pub permanent: bool, @@ -19,7 +19,7 @@ pub struct Args { #[arg(short = 'r', long, global = false)] pub recursive: bool, - /// Force removal of files without prompt + /// Force removal of item without prompt #[arg(short = 'f', long, global = false)] pub force: bool, @@ -30,15 +30,15 @@ pub struct Args { #[derive(Debug, Subcommand)] pub enum Commands { - /// List files in the trash directory + /// List items in the trash directory #[command(name = "list")] List { - /// Specify time from which to list files (in days) + /// Specify time from which to list items (in days) #[arg(short = 't', long, global = false)] time: Option, }, - /// Clean up the trash directory by removing files older than 30 days + /// Clean up the trash directory by removing files/directories older than 30 days #[command(name = "tidy")] Tidy { /// Specify time to live for tidy command (in days) (default is 30 days) @@ -49,20 +49,20 @@ pub enum Commands { /// recover all the content of the trash #[command(name = "recover-all")] RecoverAll { - /// Specify time from which to recover files (in days) + /// Specify time from which to recover files/directories (in days) #[arg(short = 't', long, global = false)] time: Option, }, - /// Recover files from the trash directory + /// Recover items from the trash directory #[command(name = "recover")] Recover { - /// Name of the file to recover + /// Name of the file/directorie to recover #[arg(help = "Name of the file to recover from trash")] name: Vec, }, - /// Purge files from the trash directory + /// Purge files/directorie from the trash directory #[command(name = "purge")] Purge { /// Purge files from the trash directory @@ -72,8 +72,8 @@ pub enum Commands { } impl Args { - /// Get the files to remove, handling the default case - pub fn get_files(&self) -> Vec { + /// Get the items to remove, handling the default case + pub fn get_items(&self) -> Vec { match &self.command { Some(_) => Vec::new(), // No files for list/tidy/recover commands None => self.file.clone(), // Use the default file argument @@ -113,7 +113,7 @@ impl Args { matches!(self.command, Some(Commands::Purge { .. })) } - /// Get the name to purge (if purge command is active) + /// Get the name of the item to purge (if purge command is active) pub fn get_purge_name(&self) -> Vec { match &self.command { Some(Commands::Purge { name }) => name.clone(), @@ -143,7 +143,7 @@ impl Args { } } - /// Get the time from which to list files for list command + /// Get the time from which to list files/directories for list command pub fn get_time_list(&self) -> i64 { match &self.command { // Default to 0 days (which will be evaluated to all the content)if not specified diff --git a/src/main.rs b/src/main.rs index 72a2096..0cc7bab 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,14 @@ use chrono::{Local, TimeZone}; use colored::Colorize; -use tabled::{ - Table, Tabled, - settings::{Alignment, Style, object::Columns}, -}; +use std::path::Path; +use tabled::settings::object::Columns; +use tabled::settings::{Alignment, Style}; +use tabled::{Table, Tabled}; use trash::os_limited::restore_all; mod args; use args::Args; use clap::Parser; -use std::fs; +use std::{fs, path::PathBuf}; use trash::os_limited; use trash::{TrashItem, delete}; @@ -103,12 +103,77 @@ pub fn tidy_trash(days: i64) -> Result<(), trash::Error> { } Ok(()) } +pub fn resolve_conflict(path: &PathBuf) -> std::io::Result<()> { + let name = match path.file_name() { + Some(name) => name.to_string_lossy(), + None => { + eprintln!("{}", "Path does not have a valid filename".red()); + return Ok(()); + } + }; + + let timestamp = Local::now().format("%Y%m%d_%H%M%S"); + + let stem = path + .file_stem() + .map(|s| s.to_string_lossy()) + .unwrap_or_else(|| name.clone()); + + let extension = path + .extension() + .map(|ext| format!(".{}", ext.to_string_lossy())) + .unwrap_or_default(); + + let new_name = format!("{}_{}{}", stem, timestamp, extension); + + eprintln!( + "{}", + format!( + "Conflict detected: '{}' already exists in trash. Would be renamed to: '{}'", + name, new_name + ) + .yellow() + ); + + fs::rename(path, &new_name)?; + + if let Err(e) = delete(&new_name) { + eprintln!( + "{}", + format!("Error moving {} to trash: {e}", path.display()).red() + ); + } + + Ok(()) +} + +pub fn check_conflict(path: &Path) -> bool { + let name = match path.file_name() { + Some(name) => name.to_string_lossy(), + None => { + eprintln!("{}", "Path does not have a valid filename".red()); + return false; + } + }; + + let trash_list = match os_limited::list() { + Ok(items) => items, + Err(e) => { + eprintln!("{}", format!("Error listing trash items: {e}").red()); + return false; + } + }; + + trash_list + .iter() + .any(|item| item.name.to_string_lossy() == name) +} fn main() { // parsing the args let args = Args::parse(); - let paths = args.get_files(); + let paths = args.get_items(); let recursive = args.recursive; let force = args.force; let dir = args.dir; @@ -286,9 +351,21 @@ All the contents from the trash more then {days} days will be deleted permanentl } } (false, _, _, _, _) => { - if let Err(e) = delete(&path) { - eprintln!("{}", format!("Error moving to trash: {e}").red()); - } + match check_conflict(&path) { + true => { + if let Err(e) = resolve_conflict(&path) { + eprintln!("{}", format!("Error resolving conflict: {e}").red()); + } + } + false => { + if let Err(e) = delete(&path) { + eprintln!( + "{}", + format!("Error moving {} to trash: {e}", path.display()).red() + ); + } + } + }; } _ => {} }