intro merge action in decompression

Signed-off-by: tommady <tommady@users.noreply.github.com>
This commit is contained in:
tommady 2025-04-18 11:58:00 +00:00
parent 267ce7672e
commit dd81c0c449
No known key found for this signature in database
GPG Key ID: 175B664929DF2F2F
7 changed files with 58 additions and 30 deletions

View File

@ -18,8 +18,6 @@ pub fn unpack_archive(
password: Option<&[u8]>, password: Option<&[u8]>,
quiet: bool, quiet: bool,
) -> crate::Result<usize> { ) -> crate::Result<usize> {
assert!(output_folder.read_dir().expect("dir exists").next().is_none());
let archive = match password { let archive = match password {
Some(password) => Archive::with_password(archive_path, password), Some(password) => Archive::with_password(archive_path, password),
None => Archive::new(archive_path), None => Archive::new(archive_path),

View File

@ -24,7 +24,6 @@ use crate::{
/// Unpacks the archive given by `archive` into the folder given by `into`. /// Unpacks the archive given by `archive` into the folder given by `into`.
/// Assumes that output_folder is empty /// Assumes that output_folder is empty
pub fn unpack_archive(reader: Box<dyn Read>, output_folder: &Path, quiet: bool) -> crate::Result<usize> { pub fn unpack_archive(reader: Box<dyn Read>, output_folder: &Path, quiet: bool) -> crate::Result<usize> {
assert!(output_folder.read_dir().expect("dir exists").next().is_none());
let mut archive = tar::Archive::new(reader); let mut archive = tar::Archive::new(reader);
let mut files_unpacked = 0; let mut files_unpacked = 0;

View File

@ -37,8 +37,6 @@ pub fn unpack_archive<R>(
where where
R: Read + Seek, R: Read + Seek,
{ {
assert!(output_folder.read_dir().expect("dir exists").next().is_none());
let mut unpacked_files = 0; let mut unpacked_files = 0;
for idx in 0..archive.len() { for idx in 0..archive.len() {

View File

@ -137,7 +137,11 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> {
Gzip | Bzip | Bzip3 | Lz4 | Lzma | Snappy | Zstd | Brotli => { Gzip | Bzip | Bzip3 | Lz4 | Lzma | Snappy | Zstd | Brotli => {
reader = chain_reader_decoder(&first_extension, reader)?; 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, Some(file) => file,
None => return Ok(()), None => return Ok(()),
}; };
@ -318,7 +322,7 @@ fn unpack(
let output_dir_cleaned = if is_valid_output_dir { let output_dir_cleaned = if is_valid_output_dir {
output_dir.to_owned() output_dir.to_owned()
} else { } else {
match utils::resolve_path_conflict(output_dir, question_policy)? { match utils::resolve_path_conflict(output_dir, question_policy, QuestionAction::Decompression)? {
Some(path) => path, Some(path) => path,
None => return Ok(ControlFlow::Break(())), 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 // 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 // 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, Some(path) => path,
None => return Ok(ControlFlow::Break(())), None => return Ok(ControlFlow::Break(())),
}; };

View File

@ -20,6 +20,7 @@ use crate::{
list::ListOptions, list::ListOptions,
utils::{ utils::{
self, colors::*, is_path_stdin, logger::info_accessible, path_to_str, EscapedPathDisplay, FileVisibilityPolicy, self, colors::*, is_path_stdin, logger::info_accessible, path_to_str, EscapedPathDisplay, FileVisibilityPolicy,
QuestionAction,
}, },
CliArgs, QuestionPolicy, CliArgs, QuestionPolicy,
}; };
@ -91,10 +92,11 @@ pub fn run(
)?; )?;
check::check_archive_formats_position(&formats, &output_path)?; check::check_archive_formats_position(&formats, &output_path)?;
let output_file = match utils::ask_to_create_file(&output_path, question_policy)? { let output_file =
Some(writer) => writer, match utils::ask_to_create_file(&output_path, question_policy, QuestionAction::Compression)? {
None => return Ok(()), Some(writer) => writer,
}; None => return Ok(()),
};
let level = if fast { let level = if fast {
Some(1) // Lowest level of compression Some(1) // Lowest level of compression

View File

@ -11,7 +11,7 @@ use fs_err as fs;
use super::{question::FileConflitOperation, user_wants_to_overwrite}; use super::{question::FileConflitOperation, user_wants_to_overwrite};
use crate::{ use crate::{
extension::Extension, extension::Extension,
utils::{logger::info_accessible, EscapedPathDisplay}, utils::{logger::info_accessible, EscapedPathDisplay, QuestionAction},
QuestionPolicy, QuestionPolicy,
}; };
@ -26,9 +26,13 @@ pub fn is_path_stdin(path: &Path) -> bool {
/// * `Ok(None)` means the user wants to cancel the operation /// * `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 /// * `Ok(Some(path))` returns a valid PathBuf without any another file or directory with the same name
/// * `Err(_)` is an error /// * `Err(_)` is an error
pub fn resolve_path_conflict(path: &Path, question_policy: QuestionPolicy) -> crate::Result<Option<PathBuf>> { pub fn resolve_path_conflict(
path: &Path,
question_policy: QuestionPolicy,
question_action: QuestionAction,
) -> crate::Result<Option<PathBuf>> {
if path.exists() { 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::Cancel => Ok(None),
FileConflitOperation::Overwrite => { FileConflitOperation::Overwrite => {
remove_file_or_dir(path)?; 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); let renamed_path = rename_for_available_filename(path);
Ok(Some(renamed_path)) Ok(Some(renamed_path))
} }
FileConflitOperation::Merge => Ok(Some(path.to_path_buf())),
} }
} else { } else {
Ok(Some(path.to_path_buf())) Ok(Some(path.to_path_buf()))

View File

@ -48,49 +48,71 @@ pub enum FileConflitOperation {
/// Rename the file /// Rename the file
/// It'll be put "_1" at the end of the filename or "_2","_3","_4".. if already exists /// It'll be put "_1" at the end of the filename or "_2","_3","_4".. if already exists
Rename, Rename,
/// Merge duplicated files
Merge,
} }
/// Check if QuestionPolicy flags were set, otherwise, ask user if they want to overwrite. /// 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<FileConflitOperation> { pub fn user_wants_to_overwrite(
path: &Path,
question_policy: QuestionPolicy,
question_action: QuestionAction,
) -> crate::Result<FileConflitOperation> {
use FileConflitOperation as Op; use FileConflitOperation as Op;
match question_policy { match question_policy {
QuestionPolicy::AlwaysYes => Ok(Op::Overwrite), QuestionPolicy::AlwaysYes => Ok(Op::Overwrite),
QuestionPolicy::AlwaysNo => Ok(Op::Cancel), 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 /// Ask the user if they want to overwrite or rename the &Path
pub fn ask_file_conflict_operation(path: &Path) -> Result<FileConflitOperation> { pub fn ask_file_conflict_operation(path: &Path, question_action: QuestionAction) -> Result<FileConflitOperation> {
use FileConflitOperation as Op; use FileConflitOperation as Op;
let path = path_to_str(strip_cur_dir(path)); let path = path_to_str(strip_cur_dir(path));
match question_action {
ChoicePrompt::new( QuestionAction::Compression => ChoicePrompt::new(
format!("Do you want to overwrite {path}?"), format!("Do you want to overwrite {path}?"),
[ [
("yes", Op::Overwrite, *colors::GREEN), ("yes", Op::Overwrite, *colors::GREEN),
("no", Op::Cancel, *colors::RED), ("no", Op::Cancel, *colors::RED),
("rename", Op::Rename, *colors::BLUE), ("rename", Op::Rename, *colors::BLUE),
], ],
) )
.ask() .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. /// 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)`] /// 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<Option<fs::File>> { pub fn ask_to_create_file(
path: &Path,
question_policy: QuestionPolicy,
question_action: QuestionAction,
) -> Result<Option<fs::File>> {
match fs::OpenOptions::new().write(true).create_new(true).open(path) { match fs::OpenOptions::new().write(true).create_new(true).open(path) {
Ok(w) => Ok(Some(w)), Ok(w) => Ok(Some(w)),
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => { Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {
let action = match question_policy { let action = match question_policy {
QuestionPolicy::AlwaysYes => FileConflitOperation::Overwrite, QuestionPolicy::AlwaysYes => FileConflitOperation::Overwrite,
QuestionPolicy::AlwaysNo => FileConflitOperation::Cancel, QuestionPolicy::AlwaysNo => FileConflitOperation::Cancel,
QuestionPolicy::Ask => ask_file_conflict_operation(path)?, QuestionPolicy::Ask => ask_file_conflict_operation(path, question_action)?,
}; };
match action { match action {
FileConflitOperation::Merge => Ok(Some(fs::File::create(path)?)),
FileConflitOperation::Overwrite => { FileConflitOperation::Overwrite => {
utils::remove_file_or_dir(path)?; utils::remove_file_or_dir(path)?;
Ok(Some(fs::File::create(path)?)) Ok(Some(fs::File::create(path)?))