WIP decompression support for .xz, .bz, .lzma

This commit is contained in:
Vinícius Rodrigues Miguel 2021-03-22 02:18:35 -03:00
parent 6c7d24084f
commit 90ad9d8f8a
8 changed files with 178 additions and 31 deletions

View File

@ -2,8 +2,11 @@ use std::path::PathBuf;
use crate::{error::OuchResult, file::File};
/// This file should/could store a Decompressor trait
pub enum DecompressionResult {
FilesUnpacked(Vec<PathBuf>),
FileInMemory(Vec<u8>)
}
pub trait Decompressor {
fn decompress(&self, from: &File, into: &Option<File>) -> OuchResult<Vec<PathBuf>>;
fn decompress(&self, from: &File, into: &Option<File>) -> OuchResult<DecompressionResult>;
}

View File

@ -1,7 +1,10 @@
mod decompressor;
mod tar;
mod zip;
mod niffler;
pub use decompressor::Decompressor;
pub use decompressor::DecompressionResult;
pub use self::tar::TarDecompressor;
pub use self::zip::ZipDecompressor;
pub use self::zip::ZipDecompressor;
pub use self::niffler::NifflerDecompressor;

View File

@ -0,0 +1,54 @@
use std::{io::Read, path::Path};
use colored::Colorize;
use niffler;
use crate::file::File;
use crate::{
error::{self, OuchResult},
utils,
};
use super::decompressor::Decompressor;
use super::decompressor::DecompressionResult;
pub struct NifflerDecompressor {}
impl NifflerDecompressor {
fn unpack_file(from: &Path) -> OuchResult<Vec<u8>> {
println!("{}: trying to decompress {:?}", "info".yellow(), from);
let file = std::fs::read(from)?;
let (mut reader, compression) = niffler::get_reader(Box::new(&file[..]))?;
match compression {
niffler::Format::No => {
return Err(error::Error::InvalidInput);
},
other => {
println!("{}: {:?} detected.", "info".yellow(), other);
}
}
let mut buffer = Vec::new();
let bytes_read = reader.read_to_end(&mut buffer)?;
println!("{}: {:?} extracted into memory ({} bytes).", "info".yellow(), from, bytes_read);
Ok(buffer)
}
}
impl Decompressor for NifflerDecompressor {
fn decompress(&self, from: &File, into: &Option<File>) -> OuchResult<DecompressionResult> {
let destination_path = utils::get_destination_path(into);
utils::create_path_if_non_existent(destination_path)?;
let bytes = Self::unpack_file(&from.path)?;
Ok(DecompressionResult::FileInMemory(bytes))
}
}

View File

@ -9,7 +9,7 @@ use tar;
use crate::{error::OuchResult, utils};
use crate::file::File;
use super::decompressor::Decompressor;
use super::decompressor::{DecompressionResult, Decompressor};
pub struct TarDecompressor {}
@ -45,13 +45,13 @@ impl TarDecompressor {
}
impl Decompressor for TarDecompressor {
fn decompress(&self, from: &File, into: &Option<File>) -> OuchResult<Vec<PathBuf>> {
fn decompress(&self, from: &File, into: &Option<File>) -> OuchResult<DecompressionResult> {
let destination_path = utils::get_destination_path(into);
utils::create_path_if_non_existent(destination_path)?;
let files_unpacked = Self::unpack_files(&from.path, destination_path)?;
Ok(files_unpacked)
Ok(DecompressionResult::FilesUnpacked(files_unpacked))
}
}

View File

@ -6,7 +6,7 @@ use zip::{self, read::ZipFile};
use crate::{error::{self, OuchResult}, utils};
use crate::file::File;
use super::decompressor::Decompressor;
use super::decompressor::{DecompressionResult, Decompressor};
pub struct ZipDecompressor {}
@ -61,6 +61,8 @@ impl ZipDecompressor {
io::copy(&mut file, &mut outfile)?;
}
// TODO: check if permissions are correct when on Unix
let file_path = fs::canonicalize(file_path.clone())?;
unpacked_files.push(file_path);
}
@ -71,13 +73,13 @@ impl ZipDecompressor {
impl Decompressor for ZipDecompressor {
fn decompress(&self, from: &File, into: &Option<File>) -> OuchResult<Vec<PathBuf>> {
fn decompress(&self, from: &File, into: &Option<File>) -> OuchResult<DecompressionResult> {
let destination_path = utils::get_destination_path(into);
utils::create_path_if_non_existent(destination_path)?;
let files_unpacked = Self::unpack_files(&from.path, destination_path)?;
Ok(files_unpacked)
Ok(DecompressionResult::FilesUnpacked(files_unpacked))
}
}

View File

@ -15,6 +15,7 @@ pub enum Error {
InvalidZipArchive(&'static str),
PermissionDenied,
UnsupportedZipArchive(&'static str),
FileTooShort,
InputsMustHaveBeenDecompressible(String),
}
@ -70,4 +71,19 @@ impl From<zip::result::ZipError> for Error {
UnsupportedArchive(filename) => Self::UnsupportedZipArchive(filename)
}
}
}
impl From<niffler::error::Error> for Error {
fn from(err: niffler::error::Error) -> Self {
use niffler::error::Error as NifErr;
match err {
NifErr::FeatureDisabled => {
// Ouch is using Niffler with all its features so
// this should be unreachable.
unreachable!();
},
NifErr::FileTooShort => Self::FileTooShort,
NifErr::IOError(io_err) => Self::from(io_err)
}
}
}

View File

@ -1,12 +1,20 @@
use std::{ffi::OsStr, fs, io::Write};
use colored::Colorize;
use crate::{cli::{Command, CommandKind}, error, extension::CompressionFormat, file::File};
use crate::decompressors::Decompressor;
use crate::decompressors::TarDecompressor;
use crate::decompressors::ZipDecompressor;
use crate::{
cli::{Command, CommandKind},
decompressors::{DecompressionResult, NifflerDecompressor},
error::{self, OuchResult},
extension::CompressionFormat,
file::File,
utils,
};
pub struct Evaluator {
pub struct Evaluator {
command: Command,
// verbosity: Verbosity
}
@ -14,40 +22,101 @@ pub struct Evaluator {
impl Evaluator {
// todo: remove this?
pub fn new(command: Command) -> Self {
Self {
command
}
Self { command }
}
fn get_decompressor(&self, file: &File) -> error::OuchResult<Box<dyn Decompressor>> {
fn get_decompressor(
&self,
file: &File,
) -> error::OuchResult<(Option<Box<dyn Decompressor>>, Box<dyn Decompressor>)> {
if file.extension.is_none() {
// This block *should* be unreachable
eprintln!("{}: reached Evaluator::get_decompressor without known extension.", "internal error".red());
eprintln!(
"{}: reached Evaluator::get_decompressor without known extension.",
"internal error".red()
);
return Err(error::Error::InvalidInput);
}
let extension = file.extension.clone().unwrap();
let decompressor: Box<dyn Decompressor> = match extension.second_ext {
CompressionFormat::Tar => {
Box::new(TarDecompressor{})
},
CompressionFormat::Zip => {
Box::new(ZipDecompressor{})
}
_ => {
todo!()
let decompressor_from_format = |ext| -> Box<dyn Decompressor> {
match ext {
CompressionFormat::Tar => Box::new(TarDecompressor {}),
CompressionFormat::Zip => Box::new(ZipDecompressor {}),
CompressionFormat::Gzip | CompressionFormat::Lzma | CompressionFormat::Bzip => {
Box::new(NifflerDecompressor {})
}
}
};
let second_decompressor = decompressor_from_format(extension.second_ext);
Ok(decompressor)
let first_decompressor = match extension.first_ext {
Some(ext) => Some(decompressor_from_format(ext)),
None => None,
};
Ok((first_decompressor, second_decompressor))
}
// todo: move this folder into decompressors/ later on
fn decompress_file_in_memory(
bytes: Vec<u8>,
file: &File,
decompressor: Option<Box<dyn Decompressor>>,
output_file: &Option<File>,
) -> OuchResult<()> {
let output_file = utils::get_destination_path(output_file);
let mut filename = file.path.file_stem().unwrap_or(output_file.as_os_str());
if filename == "." {
// 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.
filename = OsStr::new("ouch-output");
}
if decompressor.is_none() {
// 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.
println!("{}: saving to {:?}.", "info".yellow(), filename);
let mut f = fs::File::create(output_file.join(filename))?;
f.write_all(&bytes)?;
return Ok(());
}
// If there is a decompressor to use, we'll create a file in-memory (to-do) and decompress it
// TODO: change decompressor logic to use BufReader or something like that
Ok(())
}
fn decompress_file(&self, file: &File) -> error::OuchResult<()> {
let output_file = &self.command.output;
let decompressor = self.get_decompressor(file)?;
let files_unpacked = decompressor.decompress(file, output_file)?;
let (first_decompressor, second_decompressor) = self.get_decompressor(file)?;
// TODO: decompress the first extension if it exists
let decompression_result = second_decompressor.decompress(file, output_file)?;
match decompression_result {
DecompressionResult::FileInMemory(bytes) => {
// We'll now decompress a file currently in memory.
// This will currently happen in the case of .bz, .xz and .lzma
Self::decompress_file_in_memory(bytes, file, first_decompressor, output_file)?;
}
DecompressionResult::FilesUnpacked(files) => {
// If the file's last extension was an archival method,
// such as .tar, .zip or (to-do) .rar, then we won't look for
// further processing.
// The reason for this is that cases such as "file.xz.tar" are too rare
// to worry about, at least at the moment.
// TODO: use the `files` variable for something
}
}
Ok(())
}
@ -67,4 +136,4 @@ impl Evaluator {
}
Ok(())
}
}
}

View File

@ -49,7 +49,7 @@ impl Extension {
"tar" => Ok(Tar),
"gz" => Ok(Gzip),
"bz" => Ok(Bzip),
"lzma" => Ok(Lzma),
"lz" | "lzma" => Ok(Lzma),
other => Err(error::Error::UnknownExtensionError(other.into())),
}
};