mirror of
https://github.com/ouch-org/ouch.git
synced 2025-06-05 02:55:31 +00:00
WIP decompression support for .xz, .bz, .lzma
This commit is contained in:
parent
6c7d24084f
commit
90ad9d8f8a
@ -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>;
|
||||
}
|
@ -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;
|
54
src/decompressors/niffler.rs
Normal file
54
src/decompressors/niffler.rs
Normal 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))
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
16
src/error.rs
16
src/error.rs
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
111
src/evaluator.rs
111
src/evaluator.rs
@ -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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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())),
|
||||
}
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user