mirror of
https://github.com/ouch-org/ouch.git
synced 2025-06-07 12:05:46 +00:00
Merge pull request #2 from vrmiguel/user-confirmation
Add confirmation dialogs for file overwriting
This commit is contained in:
commit
7e85798381
@ -8,7 +8,7 @@ use colored::Colorize;
|
|||||||
use tar::{self, Archive};
|
use tar::{self, Archive};
|
||||||
|
|
||||||
use super::decompressor::{DecompressionResult, Decompressor};
|
use super::decompressor::{DecompressionResult, Decompressor};
|
||||||
use crate::{file::File, utils};
|
use crate::{file::File, utils, dialogs::Confirmation};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TarDecompressor {}
|
pub struct TarDecompressor {}
|
||||||
@ -21,6 +21,7 @@ impl TarDecompressor {
|
|||||||
&from.path
|
&from.path
|
||||||
);
|
);
|
||||||
let mut files_unpacked = vec![];
|
let mut files_unpacked = vec![];
|
||||||
|
let confirm = Confirmation::new("Do you want to overwrite 'FILE'?", Some("FILE"));
|
||||||
|
|
||||||
let mut archive: Archive<Box<dyn Read>> = match from.contents_in_memory {
|
let mut archive: Archive<Box<dyn Read>> = match from.contents_in_memory {
|
||||||
Some(bytes) => tar::Archive::new(Box::new(Cursor::new(bytes))),
|
Some(bytes) => tar::Archive::new(Box::new(Cursor::new(bytes))),
|
||||||
@ -33,7 +34,15 @@ impl TarDecompressor {
|
|||||||
for file in archive.entries()? {
|
for file in archive.entries()? {
|
||||||
let mut file = file?;
|
let mut file = file?;
|
||||||
|
|
||||||
// TODO: check if file/folder already exists and ask user's permission for overwriting
|
let file_path = PathBuf::from(into).join(file.path()?);
|
||||||
|
if file_path.exists() {
|
||||||
|
let file_path_str = &*file_path.to_string_lossy();
|
||||||
|
if !confirm.ask(Some(file_path_str))? {
|
||||||
|
// The user does not want to overwrite the file
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
file.unpack_in(into)?;
|
file.unpack_in(into)?;
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
@ -43,7 +52,7 @@ impl TarDecompressor {
|
|||||||
file.size()
|
file.size()
|
||||||
);
|
);
|
||||||
|
|
||||||
let file_path = fs::canonicalize(into.join(file.path()?))?;
|
let file_path = fs::canonicalize(file_path)?;
|
||||||
files_unpacked.push(file_path);
|
files_unpacked.push(file_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ use colored::Colorize;
|
|||||||
use zip::{self, read::ZipFile, ZipArchive};
|
use zip::{self, read::ZipFile, ZipArchive};
|
||||||
|
|
||||||
use super::decompressor::{DecompressionResult, Decompressor};
|
use super::decompressor::{DecompressionResult, Decompressor};
|
||||||
use crate::{file::File, utils};
|
use crate::{dialogs::Confirmation, file::File, utils};
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
fn __unix_set_permissions(file_path: &PathBuf, file: &ZipFile) {
|
fn __unix_set_permissions(file_path: &PathBuf, file: &ZipFile) {
|
||||||
@ -41,6 +41,7 @@ impl ZipDecompressor {
|
|||||||
where
|
where
|
||||||
T: Read + Seek,
|
T: Read + Seek,
|
||||||
{
|
{
|
||||||
|
let confirm = Confirmation::new("Do you want to overwrite 'FILE'?", Some("FILE"));
|
||||||
let mut unpacked_files = vec![];
|
let mut unpacked_files = vec![];
|
||||||
for idx in 0..archive.len() {
|
for idx in 0..archive.len() {
|
||||||
let mut file = archive.by_index(idx)?;
|
let mut file = archive.by_index(idx)?;
|
||||||
@ -50,28 +51,37 @@ impl ZipDecompressor {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let file_path = into.join(file_path);
|
let file_path = into.join(file_path);
|
||||||
|
if file_path.exists() {
|
||||||
|
let file_path_str = &*file_path.as_path().to_string_lossy();
|
||||||
|
if !confirm.ask(Some(file_path_str))? {
|
||||||
|
// The user does not want to overwrite the file
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Self::check_for_comments(&file);
|
Self::check_for_comments(&file);
|
||||||
|
|
||||||
let is_dir = (&*file.name()).ends_with('/');
|
match (&*file.name()).ends_with('/') {
|
||||||
|
_is_dir @ true => {
|
||||||
if is_dir {
|
println!("File {} extracted to \"{}\"", idx, file_path.display());
|
||||||
println!("File {} extracted to \"{}\"", idx, file_path.display());
|
fs::create_dir_all(&file_path)?;
|
||||||
fs::create_dir_all(&file_path)?;
|
}
|
||||||
} else {
|
_is_file @ false => {
|
||||||
if let Some(p) = file_path.parent() {
|
if let Some(path) = file_path.parent() {
|
||||||
if !p.exists() {
|
if !path.exists() {
|
||||||
fs::create_dir_all(&p)?;
|
fs::create_dir_all(&path)?;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
println!(
|
||||||
|
"{}: \"{}\" extracted. ({} bytes)",
|
||||||
|
"info".yellow(),
|
||||||
|
file_path.display(),
|
||||||
|
file.size()
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut output_file = fs::File::create(&file_path)?;
|
||||||
|
io::copy(&mut file, &mut output_file)?;
|
||||||
}
|
}
|
||||||
println!(
|
|
||||||
"{}: \"{}\" extracted. ({} bytes)",
|
|
||||||
"info".yellow(),
|
|
||||||
file_path.display(),
|
|
||||||
file.size()
|
|
||||||
);
|
|
||||||
let mut outfile = fs::File::create(&file_path)?;
|
|
||||||
io::copy(&mut file, &mut outfile)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
47
src/dialogs.rs
Normal file
47
src/dialogs.rs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
use std::io::{self, Write};
|
||||||
|
|
||||||
|
use colored::Colorize;
|
||||||
|
|
||||||
|
pub struct Confirmation<'a> {
|
||||||
|
pub prompt: &'a str,
|
||||||
|
pub placeholder: Option<&'a str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Error;
|
||||||
|
|
||||||
|
impl<'a> Confirmation<'a> {
|
||||||
|
pub fn new(prompt: &'a str, pattern: Option<&'a str>) -> Self {
|
||||||
|
Self {
|
||||||
|
prompt,
|
||||||
|
placeholder: pattern,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ask(&self, substitute: Option<&'a str>) -> crate::Result<bool> {
|
||||||
|
let message = match (self.placeholder, substitute) {
|
||||||
|
(None, _) => self.prompt.into(),
|
||||||
|
(Some(_), None) => return Err(crate::Error::InternalError),
|
||||||
|
(Some(placeholder), Some(subs)) => self.prompt.replace(placeholder, subs),
|
||||||
|
};
|
||||||
|
|
||||||
|
loop {
|
||||||
|
print!("{} [{}/{}] ", message, "Y".bright_green(), "n".bright_red());
|
||||||
|
io::stdout().flush()?;
|
||||||
|
|
||||||
|
let mut answer = String::new();
|
||||||
|
io::stdin().read_line(&mut answer)?;
|
||||||
|
let trimmed_answer = answer.trim().to_ascii_lowercase();
|
||||||
|
|
||||||
|
if trimmed_answer.is_empty() {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
match trimmed_answer.to_ascii_lowercase().as_ref() {
|
||||||
|
"y" | "yes" => return Ok(true),
|
||||||
|
"n" | "no" => return Ok(false),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -16,6 +16,7 @@ pub enum Error {
|
|||||||
PermissionDenied,
|
PermissionDenied,
|
||||||
UnsupportedZipArchive(&'static str),
|
UnsupportedZipArchive(&'static str),
|
||||||
InputsMustHaveBeenDecompressible(PathBuf),
|
InputsMustHaveBeenDecompressible(PathBuf),
|
||||||
|
InternalError,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
@ -13,6 +13,7 @@ use crate::{
|
|||||||
TarDecompressor, ZipDecompressor,
|
TarDecompressor, ZipDecompressor,
|
||||||
},
|
},
|
||||||
extension::{CompressionFormat, Extension},
|
extension::{CompressionFormat, Extension},
|
||||||
|
dialogs::Confirmation,
|
||||||
file::File,
|
file::File,
|
||||||
utils,
|
utils,
|
||||||
};
|
};
|
||||||
@ -148,9 +149,18 @@ impl Evaluator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn compress_files(files: Vec<PathBuf>, mut output: File) -> crate::Result<()> {
|
fn compress_files(files: Vec<PathBuf>, mut output: File) -> crate::Result<()> {
|
||||||
|
let confirm = Confirmation::new("Do you want to overwrite 'FILE'?", Some("FILE"));
|
||||||
let (first_compressor, second_compressor) = Self::get_compressor(&output)?;
|
let (first_compressor, second_compressor) = Self::get_compressor(&output)?;
|
||||||
|
|
||||||
let output_path = output.path.clone();
|
let output_path = output.path.clone();
|
||||||
|
if output_path.exists() {
|
||||||
|
let output_path_str = &*output_path.to_string_lossy();
|
||||||
|
if !confirm.ask(Some(output_path_str))? {
|
||||||
|
// The user does not want to overwrite the file
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
let bytes = match first_compressor {
|
let bytes = match first_compressor {
|
||||||
Some(first_compressor) => {
|
Some(first_compressor) => {
|
||||||
|
12
src/main.rs
12
src/main.rs
@ -1,6 +1,7 @@
|
|||||||
mod cli;
|
mod cli;
|
||||||
mod compressors;
|
mod compressors;
|
||||||
mod decompressors;
|
mod decompressors;
|
||||||
|
mod dialogs;
|
||||||
mod error;
|
mod error;
|
||||||
mod evaluator;
|
mod evaluator;
|
||||||
mod extension;
|
mod extension;
|
||||||
@ -18,3 +19,14 @@ fn main() -> crate::Result<()> {
|
|||||||
let command = cli::Command::try_from(matches)?;
|
let command = cli::Command::try_from(matches)?;
|
||||||
Evaluator::evaluate(command)
|
Evaluator::evaluate(command)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fn main() -> crate::Result<()> {
|
||||||
|
// let dialog = dialogs::Confirmation::new("Do you want to overwrite 'FILE'?", Some("FILE"));
|
||||||
|
|
||||||
|
// match dialog.ask(Some("file.tar.gz"))? {
|
||||||
|
// true => println!("deleting"),
|
||||||
|
// false => println!("keeping")
|
||||||
|
// };
|
||||||
|
|
||||||
|
// Ok(())
|
||||||
|
// }
|
||||||
|
Loading…
x
Reference in New Issue
Block a user