diff --git a/src/archive/rar.rs b/src/archive/rar.rs index ec2d1e6..8c1a309 100644 --- a/src/archive/rar.rs +++ b/src/archive/rar.rs @@ -18,8 +18,6 @@ pub fn unpack_archive( password: Option<&[u8]>, quiet: bool, ) -> crate::Result { - assert!(output_folder.read_dir().expect("dir exists").next().is_none()); - let archive = match password { Some(password) => Archive::with_password(archive_path, password), None => Archive::new(archive_path), diff --git a/src/archive/tar.rs b/src/archive/tar.rs index c0d962d..fa863eb 100644 --- a/src/archive/tar.rs +++ b/src/archive/tar.rs @@ -24,7 +24,6 @@ use crate::{ /// Unpacks the archive given by `archive` into the folder given by `into`. /// Assumes that output_folder is empty pub fn unpack_archive(reader: Box, output_folder: &Path, quiet: bool) -> crate::Result { - assert!(output_folder.read_dir().expect("dir exists").next().is_none()); let mut archive = tar::Archive::new(reader); let mut files_unpacked = 0; diff --git a/src/archive/zip.rs b/src/archive/zip.rs index d593477..b9b66ca 100644 --- a/src/archive/zip.rs +++ b/src/archive/zip.rs @@ -37,8 +37,6 @@ pub fn unpack_archive( where R: Read + Seek, { - assert!(output_folder.read_dir().expect("dir exists").next().is_none()); - let mut unpacked_files = 0; for idx in 0..archive.len() { diff --git a/src/commands/decompress.rs b/src/commands/decompress.rs index 036d1d2..d7b2171 100644 --- a/src/commands/decompress.rs +++ b/src/commands/decompress.rs @@ -137,7 +137,11 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> { Gzip | Bzip | Bzip3 | Lz4 | Lzma | Snappy | Zstd | Brotli => { reader = chain_reader_decoder(&first_extension, reader)?; - let mut writer = match utils::ask_to_create_file(&options.output_file_path, options.question_policy)? { + let mut writer = match utils::ask_to_create_file( + &options.output_file_path, + options.question_policy, + QuestionAction::Decompression, + )? { Some(file) => file, None => return Ok(()), }; @@ -318,7 +322,7 @@ fn unpack( let output_dir_cleaned = if is_valid_output_dir { output_dir.to_owned() } else { - match utils::resolve_path_conflict(output_dir, question_policy)? { + match utils::resolve_path_conflict(output_dir, question_policy, QuestionAction::Decompression)? { Some(path) => path, None => return Ok(ControlFlow::Break(())), } @@ -374,7 +378,7 @@ fn smart_unpack( // Before moving, need to check if a file with the same name already exists // If it does, need to ask the user what to do - new_path = match utils::resolve_path_conflict(&new_path, question_policy)? { + new_path = match utils::resolve_path_conflict(&new_path, question_policy, QuestionAction::Decompression)? { Some(path) => path, None => return Ok(ControlFlow::Break(())), }; diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 3e8718b..063898b 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -20,6 +20,7 @@ use crate::{ list::ListOptions, utils::{ self, colors::*, is_path_stdin, logger::info_accessible, path_to_str, EscapedPathDisplay, FileVisibilityPolicy, + QuestionAction, }, CliArgs, QuestionPolicy, }; @@ -91,10 +92,11 @@ pub fn run( )?; check::check_archive_formats_position(&formats, &output_path)?; - let output_file = match utils::ask_to_create_file(&output_path, question_policy)? { - Some(writer) => writer, - None => return Ok(()), - }; + let output_file = + match utils::ask_to_create_file(&output_path, question_policy, QuestionAction::Compression)? { + Some(writer) => writer, + None => return Ok(()), + }; let level = if fast { Some(1) // Lowest level of compression diff --git a/src/utils/fs.rs b/src/utils/fs.rs index c6ffb8a..eb40625 100644 --- a/src/utils/fs.rs +++ b/src/utils/fs.rs @@ -11,7 +11,7 @@ use fs_err as fs; use super::{question::FileConflitOperation, user_wants_to_overwrite}; use crate::{ extension::Extension, - utils::{logger::info_accessible, EscapedPathDisplay}, + utils::{logger::info_accessible, EscapedPathDisplay, QuestionAction}, QuestionPolicy, }; @@ -26,9 +26,13 @@ pub fn is_path_stdin(path: &Path) -> bool { /// * `Ok(None)` means the user wants to cancel the operation /// * `Ok(Some(path))` returns a valid PathBuf without any another file or directory with the same name /// * `Err(_)` is an error -pub fn resolve_path_conflict(path: &Path, question_policy: QuestionPolicy) -> crate::Result> { +pub fn resolve_path_conflict( + path: &Path, + question_policy: QuestionPolicy, + question_action: QuestionAction, +) -> crate::Result> { if path.exists() { - match user_wants_to_overwrite(path, question_policy)? { + match user_wants_to_overwrite(path, question_policy, question_action)? { FileConflitOperation::Cancel => Ok(None), FileConflitOperation::Overwrite => { remove_file_or_dir(path)?; @@ -38,6 +42,7 @@ pub fn resolve_path_conflict(path: &Path, question_policy: QuestionPolicy) -> cr let renamed_path = rename_for_available_filename(path); Ok(Some(renamed_path)) } + FileConflitOperation::Merge => Ok(Some(path.to_path_buf())), } } else { Ok(Some(path.to_path_buf())) diff --git a/src/utils/question.rs b/src/utils/question.rs index 27eb7bb..d95595a 100644 --- a/src/utils/question.rs +++ b/src/utils/question.rs @@ -48,49 +48,71 @@ pub enum FileConflitOperation { /// Rename the file /// It'll be put "_1" at the end of the filename or "_2","_3","_4".. if already exists Rename, + /// Merge duplicated files + Merge, } /// Check if QuestionPolicy flags were set, otherwise, ask user if they want to overwrite. -pub fn user_wants_to_overwrite(path: &Path, question_policy: QuestionPolicy) -> crate::Result { +pub fn user_wants_to_overwrite( + path: &Path, + question_policy: QuestionPolicy, + question_action: QuestionAction, +) -> crate::Result { use FileConflitOperation as Op; match question_policy { QuestionPolicy::AlwaysYes => Ok(Op::Overwrite), QuestionPolicy::AlwaysNo => Ok(Op::Cancel), - QuestionPolicy::Ask => ask_file_conflict_operation(path), + QuestionPolicy::Ask => ask_file_conflict_operation(path, question_action), } } /// Ask the user if they want to overwrite or rename the &Path -pub fn ask_file_conflict_operation(path: &Path) -> Result { +pub fn ask_file_conflict_operation(path: &Path, question_action: QuestionAction) -> Result { use FileConflitOperation as Op; let path = path_to_str(strip_cur_dir(path)); - - ChoicePrompt::new( - format!("Do you want to overwrite {path}?"), - [ - ("yes", Op::Overwrite, *colors::GREEN), - ("no", Op::Cancel, *colors::RED), - ("rename", Op::Rename, *colors::BLUE), - ], - ) - .ask() + match question_action { + QuestionAction::Compression => ChoicePrompt::new( + format!("Do you want to overwrite {path}?"), + [ + ("yes", Op::Overwrite, *colors::GREEN), + ("no", Op::Cancel, *colors::RED), + ("rename", Op::Rename, *colors::BLUE), + ], + ) + .ask(), + QuestionAction::Decompression => ChoicePrompt::new( + format!("Do you want to overwrite {path}?"), + [ + ("yes", Op::Overwrite, *colors::GREEN), + ("no", Op::Cancel, *colors::RED), + ("rename", Op::Rename, *colors::BLUE), + ("merge", Op::Merge, *colors::ORANGE), + ], + ) + .ask(), + } } /// Create the file if it doesn't exist and if it does then ask to overwrite it. /// If the user doesn't want to overwrite then we return [`Ok(None)`] -pub fn ask_to_create_file(path: &Path, question_policy: QuestionPolicy) -> Result> { +pub fn ask_to_create_file( + path: &Path, + question_policy: QuestionPolicy, + question_action: QuestionAction, +) -> Result> { match fs::OpenOptions::new().write(true).create_new(true).open(path) { Ok(w) => Ok(Some(w)), Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => { let action = match question_policy { QuestionPolicy::AlwaysYes => FileConflitOperation::Overwrite, QuestionPolicy::AlwaysNo => FileConflitOperation::Cancel, - QuestionPolicy::Ask => ask_file_conflict_operation(path)?, + QuestionPolicy::Ask => ask_file_conflict_operation(path, question_action)?, }; match action { + FileConflitOperation::Merge => Ok(Some(fs::File::create(path)?)), FileConflitOperation::Overwrite => { utils::remove_file_or_dir(path)?; Ok(Some(fs::File::create(path)?))