Add support for Bzip compression (includes .tar.bz2 and .zip.bz2 and etc)

This commit is contained in:
Vinícius Rodrigues Miguel 2021-03-24 14:33:43 -03:00
parent 433f8b05b0
commit bdc16fdb17
7 changed files with 169 additions and 44 deletions

103
src/compressors/bzip.rs Normal file
View File

@ -0,0 +1,103 @@
use std::{fs, io::{self, Read, Write}, path::PathBuf};
use colored::Colorize;
use crate::{error::{Error, OuchResult}, extension::CompressionFormat, file::File};
use crate::utils::ensure_exists;
use super::{Compressor, Entry};
pub struct BzipCompressor {}
struct CompressorToMemory {}
// impl CompressorToMemory {
// pub fn compress_files(files: Vec<PathBuf>, format: CompressionFormat) -> OuchResult<Vec<u8>> {
// let mut buffer = vec![];
// if files.len() != 1 {
// eprintln!("{}: cannot compress multiple files directly to {:#?}.\n Try using an intermediate archival method such as Tar.\n Example: filename.tar{}", "error".red(), format, format);
// return Err(Error::InvalidInput);
// }
// let mut contents = Vec::new();
// let path = &files[0];
// ensure_exists(path)?;
// let bytes_read = {
// let bytes = fs::read(path)?;
// let mut encoder = get_encoder(&format, Box::new(&mut buffer));
// encoder.write_all(&*bytes)?;
// bytes.as_slice().read_to_end(&mut contents)?
// };
// println!("{}: compressed {:?} into memory ({} bytes)", "info".yellow(), &path, bytes_read);
// Ok(contents)
// }
// pub fn compress_bytes(file: File) {
// }
// }
impl BzipCompressor {
fn compress_files(files: Vec<PathBuf>, format: CompressionFormat) -> OuchResult<Vec<u8>> {
if files.len() != 1 {
eprintln!("{}: cannot compress multiple files directly to {:#?}.\n Try using an intermediate archival method such as Tar.\n Example: filename.tar{}", "error".red(), format, format);
return Err(Error::InvalidInput);
}
let path = &files[0];
ensure_exists(path)?;
let contents = {
let bytes = fs::read(path)?;
Self::compress_bytes(&*bytes)?
};
println!("{}: compressed {:?} into memory ({} bytes)", "info".yellow(), &path, contents.len());
Ok(contents)
}
fn compress_file_in_memory(file: File) -> OuchResult<Vec<u8>> {
// Ensure that our file has in-memory content
let bytes = match file.contents_in_memory {
Some(bytes) => bytes,
None => {
// TODO: error message,
return Err(Error::InvalidInput);
}
};
Ok(Self::compress_bytes(&*bytes)?)
}
fn compress_bytes(bytes: &[u8]) -> OuchResult<Vec<u8>> {
let buffer = vec![];
let mut encoder = bzip2::write::BzEncoder::new(buffer, bzip2::Compression::new(6));
encoder.write_all(bytes)?;
Ok(encoder.finish()?)
}
}
// TODO: customizable compression level
fn get_encoder<'a>(format: &CompressionFormat, buffer: Box<dyn io::Write + Send + 'a>) -> Box<dyn io::Write + Send + 'a> {
match format {
CompressionFormat::Bzip => Box::new(bzip2::write::BzEncoder::new(buffer, bzip2::Compression::new(4))),
_other => unreachable!()
}
}
impl Compressor for BzipCompressor {
fn compress(&self, from: Entry) -> OuchResult<Vec<u8>> {
match from {
Entry::Files(files) => Ok(
Self::compress_files(files, CompressionFormat::Bzip)?
),
Entry::InMemory(file) => Ok(
Self::compress_file_in_memory(file)?
),
}
}
}

View File

@ -1,10 +1,10 @@
mod tar; mod tar;
mod zip; mod zip;
mod unified; mod bzip;
mod compressor; mod compressor;
pub use compressor::Compressor; pub use compressor::Compressor;
pub use self::compressor::Entry; pub use self::compressor::Entry;
pub use self::tar::TarCompressor; pub use self::tar::TarCompressor;
pub use self::zip::ZipCompressor; pub use self::zip::ZipCompressor;
pub use self::bzip::BzipCompressor;

View File

@ -16,7 +16,7 @@ use crate::{
use super::decompressor::DecompressionResult; use super::decompressor::DecompressionResult;
use super::decompressor::Decompressor; use super::decompressor::Decompressor;
pub struct UnifiedDecompressor {} struct DecompressorToMemory {}
pub struct GzipDecompressor {} pub struct GzipDecompressor {}
pub struct LzmaDecompressor {} pub struct LzmaDecompressor {}
pub struct BzipDecompressor {} pub struct BzipDecompressor {}
@ -30,7 +30,7 @@ fn get_decoder<'a>(format: CompressionFormat, buffer: Box<dyn io::Read + Send +
} }
} }
impl UnifiedDecompressor { impl DecompressorToMemory {
fn unpack_file(from: &Path, format: CompressionFormat) -> OuchResult<Vec<u8>> { fn unpack_file(from: &Path, format: CompressionFormat) -> OuchResult<Vec<u8>> {
let file = std::fs::read(from)?; let file = std::fs::read(from)?;
@ -62,18 +62,18 @@ impl UnifiedDecompressor {
impl Decompressor for GzipDecompressor { impl Decompressor for GzipDecompressor {
fn decompress(&self, from: File, into: &Option<File>) -> OuchResult<DecompressionResult> { fn decompress(&self, from: File, into: &Option<File>) -> OuchResult<DecompressionResult> {
UnifiedDecompressor::decompress(from, CompressionFormat::Gzip, into) DecompressorToMemory::decompress(from, CompressionFormat::Gzip, into)
} }
} }
impl Decompressor for BzipDecompressor { impl Decompressor for BzipDecompressor {
fn decompress(&self, from: File, into: &Option<File>) -> OuchResult<DecompressionResult> { fn decompress(&self, from: File, into: &Option<File>) -> OuchResult<DecompressionResult> {
UnifiedDecompressor::decompress(from, CompressionFormat::Bzip, into) DecompressorToMemory::decompress(from, CompressionFormat::Bzip, into)
} }
} }
impl Decompressor for LzmaDecompressor { impl Decompressor for LzmaDecompressor {
fn decompress(&self, from: File, into: &Option<File>) -> OuchResult<DecompressionResult> { fn decompress(&self, from: File, into: &Option<File>) -> OuchResult<DecompressionResult> {
UnifiedDecompressor::decompress(from, CompressionFormat::Lzma, into) DecompressorToMemory::decompress(from, CompressionFormat::Lzma, into)
} }
} }

View File

@ -6,7 +6,8 @@ use crate::compressors::{
Entry, Entry,
Compressor, Compressor,
TarCompressor, TarCompressor,
ZipCompressor ZipCompressor,
BzipCompressor,
}; };
use crate::decompressors::{ use crate::decompressors::{
@ -67,8 +68,9 @@ impl Evaluator {
// Supported second compressors: // Supported second compressors:
// any // any
let second_compressor: Box<dyn Compressor> = match extension.second_ext { let second_compressor: Box<dyn Compressor> = match extension.second_ext {
CompressionFormat::Tar => Box::new(TarCompressor {}), CompressionFormat::Tar => Box::new(TarCompressor {}),
CompressionFormat::Zip => Box::new(ZipCompressor {}), CompressionFormat::Zip => Box::new(ZipCompressor {}),
CompressionFormat::Bzip => Box::new(BzipCompressor {}),
_other => todo!() _other => todo!()
}; };

View File

@ -1,4 +1,9 @@
use std::{convert::TryFrom, ffi::OsStr, path::{Path, PathBuf}}; use std::{
convert::TryFrom,
ffi::OsStr,
fmt::Display,
path::{Path, PathBuf},
};
use crate::error; use crate::error;
use CompressionFormat::*; use CompressionFormat::*;
@ -9,15 +14,13 @@ use CompressionFormat::*;
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct Extension { pub struct Extension {
pub first_ext: Option<CompressionFormat>, pub first_ext: Option<CompressionFormat>,
pub second_ext: CompressionFormat pub second_ext: CompressionFormat,
} }
pub fn get_extension_from_filename(filename: &str) -> Option<(&str, &str)> { pub fn get_extension_from_filename(filename: &str) -> Option<(&str, &str)> {
let path = Path::new(filename); let path = Path::new(filename);
let ext = path let ext = path.extension().and_then(OsStr::to_str)?;
.extension()
.and_then(OsStr::to_str)?;
let previous_extension = path let previous_extension = path
.file_stem() .file_stem()
@ -35,34 +38,28 @@ impl From<CompressionFormat> for Extension {
fn from(second_ext: CompressionFormat) -> Self { fn from(second_ext: CompressionFormat) -> Self {
Self { Self {
first_ext: None, first_ext: None,
second_ext second_ext,
} }
} }
} }
impl Extension { impl Extension {
pub fn new(filename: &str) -> error::OuchResult<Self> { pub fn new(filename: &str) -> error::OuchResult<Self> {
let ext_from_str = |ext| { let ext_from_str = |ext| match ext {
match ext { "zip" => Ok(Zip),
"zip" => Ok(Zip), "tar" => Ok(Tar),
"tar" => Ok(Tar), "gz" => Ok(Gzip),
"gz" => Ok(Gzip), "bz" | "bz2" => Ok(Bzip),
"bz" | "bz2" => Ok(Bzip), "lz" | "lzma" => Ok(Lzma),
"lz" | "lzma" => Ok(Lzma), other => Err(error::Error::UnknownExtensionError(other.into())),
other => Err(error::Error::UnknownExtensionError(other.into())),
}
}; };
let (first_ext, second_ext) = match get_extension_from_filename(filename) { let (first_ext, second_ext) = match get_extension_from_filename(filename) {
Some(extension_tuple) => { Some(extension_tuple) => match extension_tuple {
match extension_tuple { ("", snd) => (None, snd),
("", snd) => (None, snd), (fst, snd) => (Some(fst), snd),
(fst, snd)=> (Some(fst), snd)
}
}, },
None => { None => return Err(error::Error::MissingExtensionError(filename.into())),
return Err(error::Error::MissingExtensionError(filename.into()))
}
}; };
let (first_ext, second_ext) = match (first_ext, second_ext) { let (first_ext, second_ext) = match (first_ext, second_ext) {
@ -77,12 +74,10 @@ impl Extension {
} }
}; };
Ok( Ok(Self {
Self { first_ext,
first_ext, second_ext,
second_ext })
}
)
} }
} }
@ -123,7 +118,6 @@ impl TryFrom<&PathBuf> for CompressionFormat {
type Error = error::Error; type Error = error::Error;
fn try_from(ext: &PathBuf) -> Result<Self, Self::Error> { fn try_from(ext: &PathBuf) -> Result<Self, Self::Error> {
let ext = match ext.extension() { let ext = match ext.extension() {
Some(ext) => ext, Some(ext) => ext,
None => { None => {
@ -147,3 +141,19 @@ impl TryFrom<&str> for CompressionFormat {
extension_from_os_str(ext) extension_from_os_str(ext)
} }
} }
impl Display for CompressionFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Gzip => ".gz",
Bzip => ".bz",
Lzma => ".lz",
Tar => ".tar",
Zip => ".zip",
}
)
}
}

View File

@ -3,6 +3,16 @@ use std::{fs, path::Path};
use colored::Colorize; use colored::Colorize;
use crate::{error::OuchResult, file::File}; use crate::{error::OuchResult, file::File};
pub (crate) fn ensure_exists<'a, P>(path: P) -> OuchResult<()>
where
P: AsRef<Path> + 'a {
let exists = path.as_ref().exists();
if !exists {
eprintln!("{}: could not find file {:?}", "error".red(), path.as_ref());
}
Ok(())
}
pub (crate) fn create_path_if_non_existent(path: &Path) -> OuchResult<()> { pub (crate) fn create_path_if_non_existent(path: &Path) -> OuchResult<()> {
if !path.exists() { if !path.exists() {
println!( println!(