From c7ec9fb8def377bbb274247381f406bce24c8a6b Mon Sep 17 00:00:00 2001 From: Kartik Nayak Date: Sat, 28 Sep 2024 19:09:27 +0530 Subject: [PATCH 1/3] fix(extract): preserve full folder names during extraction, fixes #504 --- src/operation.rs | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/operation.rs b/src/operation.rs index c119e7f..27a32e1 100644 --- a/src/operation.rs +++ b/src/operation.rs @@ -91,6 +91,17 @@ fn handle_progress_state( } } +fn get_directory_name(file_name: &str) -> &str { + const SUPPORTED_EXTENSIONS: [&str; 4] = [".tar.gz", ".tgz", ".tar", ".zip"]; + + for ext in &SUPPORTED_EXTENSIONS { + if file_name.ends_with(ext) { + return &file_name[..file_name.len() - ext.len()]; + } + } + file_name +} + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum ReplaceResult { Replace(bool), @@ -683,12 +694,10 @@ impl Operation { let to = to.to_owned(); - if let Some(file_stem) = path.file_stem() { - let mut new_dir = to.join(file_stem); - // Make sure all extension parts are removed (file_stem may still contain them) - while new_dir.extension().is_some() { - new_dir.set_extension(""); - } + if let Some(file_name) = path.file_name().and_then(|f| f.to_str()) { + let dir_name = get_directory_name(file_name); + let mut new_dir = to.join(dir_name); + if new_dir.exists() { if let Some(new_dir_parent) = new_dir.parent() { new_dir = copy_unique_path(&new_dir, new_dir_parent); @@ -702,21 +711,21 @@ impl Operation { .map(io::BufReader::new) .map(flate2::read::GzDecoder::new) .map(tar::Archive::new) - .and_then(|mut archive| archive.unpack(new_dir)) + .and_then(|mut archive| archive.unpack(&new_dir)) .map_err(err_str)? } "application/x-tar" => fs::File::open(path) .map(io::BufReader::new) .map(tar::Archive::new) - .and_then(|mut archive| archive.unpack(new_dir)) + .and_then(|mut archive| archive.unpack(&new_dir)) .map_err(err_str)?, "application/zip" => fs::File::open(path) .map(io::BufReader::new) .map(zip::ZipArchive::new) .map_err(err_str)? - .and_then(|mut archive| archive.extract(new_dir)) + .and_then(|mut archive| archive.extract(&new_dir)) .map_err(err_str)?, - #[cfg(feature = "bzip2")] + #[cfg(feature = "bzip2")] "application/x-bzip" | "application/x-bzip-compressed-tar" => { fs::File::open(path) .map(io::BufReader::new) From dd744d09a1313a81cfbc7377b0da658f18293b46 Mon Sep 17 00:00:00 2001 From: Kartik Nayak Date: Sat, 28 Sep 2024 19:28:33 +0530 Subject: [PATCH 2/3] fix(extract): correctly append copy suffix for existing folder names --- src/operation.rs | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/src/operation.rs b/src/operation.rs index 27a32e1..e17b914 100644 --- a/src/operation.rs +++ b/src/operation.rs @@ -330,30 +330,21 @@ async fn copy_or_move( fn copy_unique_path(from: &Path, to: &Path) -> PathBuf { let mut to = to.to_owned(); - // Separate the full file name into its file name plus extension. - // `[Path::file_stem]` returns the full name for dotfiles (e.g. - // .someconf is the file name) - if let (Some(stem), ext) = ( - // FIXME: Replace `[Path::file_stem]` with `[Path::file_prefix]` when stablized to handle .tar.gz et al. better - from.file_stem().and_then(|name| name.to_str()), - from.extension() - .and_then(|ext| ext.to_str()) - .unwrap_or_default(), - ) { - // '.' needs to be re-added for paths with extensions. - let dot = if ext.is_empty() { "" } else { "." }; + if let Some(file_name) = from.file_name().and_then(|name| name.to_str()) { let mut n = 0u32; - // Loop until a valid `copy n` variant is found loop { n = if let Some(n) = n.checked_add(1) { n } else { - // TODO: Return error? fs_extra will handle it anyway break to; }; - // Rebuild file name - let new_name = format!("{stem} ({} {n}){dot}{ext}", fl!("copy_noun")); + let new_name = if n == 0 { + file_name.to_string() + } else { + format!("{} ({} {})", file_name, fl!("copy_noun"), n) + }; + to = to.join(new_name); if !matches!(to.try_exists(), Ok(true)) { @@ -725,7 +716,7 @@ impl Operation { .map_err(err_str)? .and_then(|mut archive| archive.extract(&new_dir)) .map_err(err_str)?, - #[cfg(feature = "bzip2")] + #[cfg(feature = "bzip2")] "application/x-bzip" | "application/x-bzip-compressed-tar" => { fs::File::open(path) .map(io::BufReader::new) From fa5eb850673ac446600b9bd170cdefb88446d88e Mon Sep 17 00:00:00 2001 From: Kartik Nayak Date: Sun, 29 Sep 2024 04:00:18 +0530 Subject: [PATCH 3/3] fix(copy_unique_path): update file rename logic to format as "filename (copy n).ext" for duplicates --- src/operation.rs | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/src/operation.rs b/src/operation.rs index e17b914..bb59e1a 100644 --- a/src/operation.rs +++ b/src/operation.rs @@ -331,18 +331,35 @@ async fn copy_or_move( fn copy_unique_path(from: &Path, to: &Path) -> PathBuf { let mut to = to.to_owned(); if let Some(file_name) = from.file_name().and_then(|name| name.to_str()) { + let is_dir = from.is_dir(); + let (stem, ext) = if !is_dir { + match from.extension().and_then(|e| e.to_str()) { + Some(ext) => { + let stem = from + .file_stem() + .and_then(|s| s.to_str()) + .unwrap_or(file_name); + (stem.to_string(), Some(ext.to_string())) + } + None => (file_name.to_string(), None), + } + } else { + (file_name.to_string(), None) + }; + let mut n = 0u32; loop { - n = if let Some(n) = n.checked_add(1) { - n - } else { - break to; - }; - let new_name = if n == 0 { file_name.to_string() } else { - format!("{} ({} {})", file_name, fl!("copy_noun"), n) + if is_dir { + format!("{} ({} {})", file_name, fl!("copy_noun"), n) + } else { + match &ext { + Some(ext) => format!("{} ({} {}).{}", stem, fl!("copy_noun"), n, ext), + None => format!("{} ({} {})", stem, fl!("copy_noun"), n), + } + } }; to = to.join(new_name); @@ -352,6 +369,12 @@ fn copy_unique_path(from: &Path, to: &Path) -> PathBuf { } // Continue if a copy with index exists to.pop(); + + n = if let Some(n) = n.checked_add(1) { + n + } else { + break to; + }; } } else { to