Add support for decompressing .tar.{bz, xz, lz} and .zip.{bz, xz, lz}

This commit is contained in:
Vinícius Rodrigues Miguel 2021-03-22 04:46:54 -03:00
parent 77d7613967
commit e08703850c
9 changed files with 108 additions and 50 deletions

View File

@ -91,6 +91,7 @@ impl TryFrom<clap::ArgMatches<'static>> for Command {
let output_file = matches.value_of("output").unwrap(); // Safe unwrap since we've established that output was supplied let output_file = matches.value_of("output").unwrap(); // Safe unwrap since we've established that output was supplied
let output_file_extension = Extension::new(output_file); let output_file_extension = Extension::new(output_file);
let output_is_compressible = output_file_extension.is_ok(); let output_is_compressible = output_file_extension.is_ok();
if output_is_compressible { if output_is_compressible {
// The supplied output is compressible, so we'll compress our inputs to it // The supplied output is compressible, so we'll compress our inputs to it
@ -103,14 +104,6 @@ impl TryFrom<clap::ArgMatches<'static>> for Command {
let input_files = input_files.map(PathBuf::from).collect(); let input_files = input_files.map(PathBuf::from).collect();
// return Ok(Command {
// kind: CommandKind::Compression(input_files),
// output: Some(File::WithExtension((
// output_file.into(),
// output_file_extension.unwrap(),
// ))),
// });
return Ok(Command { return Ok(Command {
kind: CommandKind::Compression(input_files), kind: CommandKind::Compression(input_files),
output: Some(File { output: Some(File {

View File

@ -8,5 +8,5 @@ pub enum DecompressionResult {
} }
pub trait Decompressor { pub trait Decompressor {
fn decompress(&self, from: &File, into: &Option<File>) -> OuchResult<DecompressionResult>; fn decompress(&self, from: File, into: &Option<File>) -> OuchResult<DecompressionResult>;
} }

View File

@ -42,7 +42,7 @@ impl NifflerDecompressor {
} }
impl Decompressor for NifflerDecompressor { impl Decompressor for NifflerDecompressor {
fn decompress(&self, from: &File, into: &Option<File>) -> OuchResult<DecompressionResult> { fn decompress(&self, from: File, into: &Option<File>) -> OuchResult<DecompressionResult> {
let destination_path = utils::get_destination_path(into); let destination_path = utils::get_destination_path(into);
utils::create_path_if_non_existent(destination_path)?; utils::create_path_if_non_existent(destination_path)?;

View File

@ -12,13 +12,20 @@ pub struct TarDecompressor {}
impl TarDecompressor { impl TarDecompressor {
fn unpack_files(from: &File, into: &Path) -> OuchResult<Vec<PathBuf>> { fn unpack_files(from: File, into: &Path) -> OuchResult<Vec<PathBuf>> {
println!("{}: attempting to decompress {:?}", "ouch".bright_blue(), from); println!("{}: attempting to decompress {:?}", "ouch".bright_blue(), &from.path);
let mut files_unpacked = vec![]; let mut files_unpacked = vec![];
let mut archive: Archive<Box<dyn Read>> = match from.contents {
Some(bytes) => {
tar::Archive::new(Box::new(Cursor::new(bytes)))
}
None => {
let file = fs::File::open(&from.path)?; let file = fs::File::open(&from.path)?;
let mut archive = tar::Archive::new(file); tar::Archive::new(Box::new(file))
}
};
for file in archive.entries()? { for file in archive.entries()? {
let mut file = file?; let mut file = file?;
@ -42,12 +49,12 @@ impl TarDecompressor {
} }
impl Decompressor for TarDecompressor { impl Decompressor for TarDecompressor {
fn decompress(&self, from: &File, into: &Option<File>) -> OuchResult<DecompressionResult> { fn decompress(&self, from: File, into: &Option<File>) -> OuchResult<DecompressionResult> {
let destination_path = utils::get_destination_path(into); let destination_path = utils::get_destination_path(into);
utils::create_path_if_non_existent(destination_path)?; utils::create_path_if_non_existent(destination_path)?;
let files_unpacked = Self::unpack_files(&from, destination_path)?; let files_unpacked = Self::unpack_files(from, destination_path)?;
Ok(DecompressionResult::FilesUnpacked(files_unpacked)) Ok(DecompressionResult::FilesUnpacked(files_unpacked))
} }

View File

@ -1,33 +1,36 @@
use std::{fs, io, path::{Path, PathBuf}}; use std::{fs, io::{self, Cursor, Read, Seek}, path::{Path, PathBuf}};
use colored::Colorize; use colored::Colorize;
use zip::{self, read::ZipFile}; use zip::{self, ZipArchive, read::ZipFile};
use crate::{error::{self, OuchResult}, utils}; use crate::{error, file::File};
use crate::file::File; use crate::{error::OuchResult, utils};
use super::decompressor::{DecompressionResult, Decompressor}; use super::decompressor::{DecompressionResult, Decompressor};
pub struct ZipDecompressor {} pub struct ZipDecompressor {}
impl ZipDecompressor { impl ZipDecompressor {
fn check_for_comments(file: &ZipFile) { fn check_for_comments(file: &ZipFile) {
let comment = file.comment(); let comment = file.comment();
if !comment.is_empty() { if !comment.is_empty() {
println!("{}: Comment in {}: {}", "info".yellow(), file.name(), comment); println!(
"{}: Comment in {}: {}",
"info".yellow(),
file.name(),
comment
);
} }
} }
fn unpack_files(from: &Path, into: &Path) -> OuchResult<Vec<PathBuf>> { pub fn zip_decompress<T>(
archive: &mut ZipArchive<T>,
into: &Path,
) -> error::OuchResult<Vec<PathBuf>>
where
T: Read + Seek,
{
let mut unpacked_files = vec![]; let mut unpacked_files = vec![];
println!("{}: attempting to decompress {:?}", "ouch".bright_blue(), from);
let file = fs::File::open(from)?;
let mut archive = zip::ZipArchive::new(file)?;
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)?;
let file_path = match file.enclosed_name() { let file_path = match file.enclosed_name() {
@ -68,16 +71,39 @@ impl ZipDecompressor {
Ok(unpacked_files) Ok(unpacked_files)
} }
fn unpack_files(from: File, into: &Path) -> OuchResult<Vec<PathBuf>> {
println!(
"{}: attempting to decompress {:?}",
"ouch".bright_blue(),
from
);
match from.contents {
Some(bytes) => {
let mut archive = zip::ZipArchive::new(Cursor::new(bytes))?;
Ok(Self::zip_decompress(&mut archive, into)?)
},
None => {
let file = fs::File::open(&from.path)?;
let mut archive = zip::ZipArchive::new(file)?;
Ok(Self::zip_decompress(&mut archive, into)?)
}
} }
}
}
impl Decompressor for ZipDecompressor { impl Decompressor for ZipDecompressor {
fn decompress(&self, from: &File, into: &Option<File>) -> OuchResult<DecompressionResult> { fn decompress(&self, from: File, into: &Option<File>) -> OuchResult<DecompressionResult> {
let destination_path = utils::get_destination_path(into); let destination_path = utils::get_destination_path(into);
utils::create_path_if_non_existent(destination_path)?; utils::create_path_if_non_existent(destination_path)?;
let files_unpacked = Self::unpack_files(&from.path, destination_path)?; let files_unpacked = Self::unpack_files(from, destination_path)?;
Ok(DecompressionResult::FilesUnpacked(files_unpacked)) Ok(DecompressionResult::FilesUnpacked(files_unpacked))
} }

View File

@ -1,8 +1,8 @@
use std::{ffi::OsStr, fs, io::Write}; use std::{ffi::OsStr, fs, io::Write, path::PathBuf};
use colored::Colorize; use colored::Colorize;
use crate::decompressors::Decompressor; use crate::{decompressors::Decompressor, extension::Extension};
use crate::decompressors::TarDecompressor; use crate::decompressors::TarDecompressor;
use crate::decompressors::ZipDecompressor; use crate::decompressors::ZipDecompressor;
use crate::{ use crate::{
@ -15,7 +15,6 @@ use crate::{
}; };
pub struct Evaluator { pub struct Evaluator {
command: Command,
// verbosity: Verbosity // verbosity: Verbosity
} }
@ -31,7 +30,7 @@ impl Evaluator {
); );
return Err(error::Error::InvalidInput); return Err(error::Error::InvalidInput);
} }
let extension = file.extension.clone().unwrap(); let extension = Extension::new(&file.path.to_str().unwrap())?;
let decompressor_from_format = |ext| -> Box<dyn Decompressor> { let decompressor_from_format = |ext| -> Box<dyn Decompressor> {
match ext { match ext {
@ -58,33 +57,46 @@ impl Evaluator {
// todo: move this folder into decompressors/ later on // todo: move this folder into decompressors/ later on
fn decompress_file_in_memory( fn decompress_file_in_memory(
bytes: Vec<u8>, bytes: Vec<u8>,
file: &File, file_path: PathBuf,
decompressor: Option<Box<dyn Decompressor>>, decompressor: Option<Box<dyn Decompressor>>,
output_file: &Option<File>, output_file: &Option<File>,
extension: Option<Extension>,
) -> OuchResult<()> { ) -> OuchResult<()> {
let output_file = utils::get_destination_path(output_file); let output_file_path = utils::get_destination_path(output_file);
let mut filename = file.path.file_stem().unwrap_or(output_file.as_os_str()); let mut filename = file_path.file_stem().unwrap_or(output_file_path.as_os_str());
if filename == "." { if filename == "." {
// I believe this is only possible when the supplied inout has a name // I believe this is only possible when the supplied inout has a name
// of the sort `.tar` or `.zip' and no output has been supplied. // of the sort `.tar` or `.zip' and no output has been supplied.
filename = OsStr::new("ouch-output"); filename = OsStr::new("ouch-output");
} }
let filename = PathBuf::from(filename);
if decompressor.is_none() { if decompressor.is_none() {
// There is no more processing to be done on the input file (or there is but currently unsupported) // There is no more processing to be done on the input file (or there is but currently unsupported)
// Therefore, we'll save what we have in memory into a file. // Therefore, we'll save what we have in memory into a file.
println!("{}: saving to {:?}.", "info".yellow(), filename); println!("{}: saving to {:?}.", "info".yellow(), filename);
let mut f = fs::File::create(output_file.join(filename))?; let mut f = fs::File::create(output_file_path.join(filename))?;
f.write_all(&bytes)?; f.write_all(&bytes)?;
return Ok(()); return Ok(());
} }
// If there is a decompressor to use, we'll create a file in-memory (to-do) and decompress it let file = File {
// TODO: change decompressor logic to use BufReader or something like that path: filename,
contents: Some(bytes),
extension,
};
let decompressor = decompressor.unwrap();
// If there is a decompressor to use, we'll create a file in-memory and decompress it
let decompression_result = decompressor.decompress(file, output_file)?;
Ok(()) Ok(())
} }
@ -93,15 +105,18 @@ impl Evaluator {
// let output_file = &command.output; // let output_file = &command.output;
let (first_decompressor, second_decompressor) = Self::get_decompressor(&file)?; let (first_decompressor, second_decompressor) = Self::get_decompressor(&file)?;
let decompression_result = second_decompressor.decompress(&file, output)?; let file_path = file.path.clone();
let extension = file.extension.clone();
let decompression_result = second_decompressor.decompress(file, output)?;
match decompression_result { match decompression_result {
DecompressionResult::FileInMemory(bytes) => { DecompressionResult::FileInMemory(bytes) => {
// We'll now decompress a file currently in memory. // We'll now decompress a file currently in memory.
// This will currently happen in the case of .bz, .xz and .lzma // This will currently happen in the case of .bz, .xz and .lzma
Self::decompress_file_in_memory(bytes, &file, first_decompressor, output)?; Self::decompress_file_in_memory(bytes, file_path, first_decompressor, output, extension)?;
} }
DecompressionResult::FilesUnpacked(files) => { DecompressionResult::FilesUnpacked(_files) => {
// If the file's last extension was an archival method, // If the file's last extension was an archival method,
// such as .tar, .zip or (to-do) .rar, then we won't look for // such as .tar, .zip or (to-do) .rar, then we won't look for
// further processing. // further processing.

View File

@ -30,5 +30,8 @@ fn main() -> OuchResult<()>{
print_error(err) print_error(err)
} }
} }
// let extension = dbg!(Extension::new("file.tar.gz"));
Ok(()) Ok(())
} }

View File

@ -84,7 +84,6 @@ mod cli {
"file2.jpeg".into(), "file2.jpeg".into(),
"file3.ok".into() "file3.ok".into()
]), ]),
// output: Some(File::WithExtension(("file.tar".into(), Extension::from(Tar))))
output: Some( output: Some(
File { File {
path: "file.tar".into(), path: "file.tar".into(),
@ -126,7 +125,7 @@ mod cli_errors {
#[cfg(test)] #[cfg(test)]
mod extension_extraction { mod extension_extraction {
use crate::error::OuchResult; use crate::{error::OuchResult, extension::Extension};
use crate::extension::CompressionFormat; use crate::extension::CompressionFormat;
use std::{convert::TryFrom, path::PathBuf, str::FromStr}; use std::{convert::TryFrom, path::PathBuf, str::FromStr};
@ -141,6 +140,21 @@ mod extension_extraction {
Ok(()) Ok(())
} }
#[test]
fn tar_gz() -> OuchResult<()> {
let extension = Extension::new("folder.tar.gz")?;
assert_eq!(
extension,
Extension {
first_ext: Some(CompressionFormat::Tar),
second_ext: CompressionFormat::Gzip
}
);
Ok(())
}
#[test] #[test]
fn tar() -> OuchResult<()> { fn tar() -> OuchResult<()> {
let path = PathBuf::from_str("pictures.tar").unwrap(); let path = PathBuf::from_str("pictures.tar").unwrap();

View File

@ -1,4 +1,4 @@
use std::{fs, path::{Component, Path, PathBuf}}; use std::{fs, path::Path};
use colored::Colorize; use colored::Colorize;
use crate::{error::OuchResult, file::File}; use crate::{error::OuchResult, file::File};