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 zip;
mod unified;
mod bzip;
mod compressor;
pub use compressor::Compressor;
pub use self::compressor::Entry;
pub use self::tar::TarCompressor;
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::Decompressor;
pub struct UnifiedDecompressor {}
struct DecompressorToMemory {}
pub struct GzipDecompressor {}
pub struct LzmaDecompressor {}
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>> {
let file = std::fs::read(from)?;
@ -62,18 +62,18 @@ impl UnifiedDecompressor {
impl Decompressor for GzipDecompressor {
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 {
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 {
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,
Compressor,
TarCompressor,
ZipCompressor
ZipCompressor,
BzipCompressor,
};
use crate::decompressors::{
@ -67,8 +68,9 @@ impl Evaluator {
// Supported second compressors:
// any
let second_compressor: Box<dyn Compressor> = match extension.second_ext {
CompressionFormat::Tar => Box::new(TarCompressor {}),
CompressionFormat::Zip => Box::new(ZipCompressor {}),
CompressionFormat::Tar => Box::new(TarCompressor {}),
CompressionFormat::Zip => Box::new(ZipCompressor {}),
CompressionFormat::Bzip => Box::new(BzipCompressor {}),
_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 CompressionFormat::*;
@ -9,16 +14,14 @@ use CompressionFormat::*;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Extension {
pub first_ext: Option<CompressionFormat>,
pub second_ext: CompressionFormat
pub second_ext: CompressionFormat,
}
pub fn get_extension_from_filename(filename: &str) -> Option<(&str, &str)> {
let path = Path::new(filename);
let ext = path
.extension()
.and_then(OsStr::to_str)?;
let path = Path::new(filename);
let ext = path.extension().and_then(OsStr::to_str)?;
let previous_extension = path
.file_stem()
.and_then(OsStr::to_str)
@ -35,34 +38,28 @@ impl From<CompressionFormat> for Extension {
fn from(second_ext: CompressionFormat) -> Self {
Self {
first_ext: None,
second_ext
second_ext,
}
}
}
impl Extension {
pub fn new(filename: &str) -> error::OuchResult<Self> {
let ext_from_str = |ext| {
match ext {
"zip" => Ok(Zip),
"tar" => Ok(Tar),
"gz" => Ok(Gzip),
"bz" | "bz2" => Ok(Bzip),
"lz" | "lzma" => Ok(Lzma),
other => Err(error::Error::UnknownExtensionError(other.into())),
}
let ext_from_str = |ext| match ext {
"zip" => Ok(Zip),
"tar" => Ok(Tar),
"gz" => Ok(Gzip),
"bz" | "bz2" => Ok(Bzip),
"lz" | "lzma" => Ok(Lzma),
other => Err(error::Error::UnknownExtensionError(other.into())),
};
let (first_ext, second_ext) = match get_extension_from_filename(filename) {
Some(extension_tuple) => {
match extension_tuple {
("", snd) => (None, snd),
(fst, snd)=> (Some(fst), snd)
}
Some(extension_tuple) => match extension_tuple {
("", snd) => (None, snd),
(fst, snd) => (Some(fst), snd),
},
None => {
return Err(error::Error::MissingExtensionError(filename.into()))
}
None => return Err(error::Error::MissingExtensionError(filename.into())),
};
let (first_ext, second_ext) = match (first_ext, second_ext) {
@ -77,12 +74,10 @@ impl Extension {
}
};
Ok(
Self {
first_ext,
second_ext
}
)
Ok(Self {
first_ext,
second_ext,
})
}
}
@ -108,7 +103,7 @@ fn extension_from_os_str(ext: &OsStr) -> Result<CompressionFormat, error::Error>
Some(str) => str,
None => return Err(error::Error::InvalidUnicode),
};
match ext {
"zip" => Ok(Zip),
"tar" => Ok(Tar),
@ -123,7 +118,6 @@ impl TryFrom<&PathBuf> for CompressionFormat {
type Error = error::Error;
fn try_from(ext: &PathBuf) -> Result<Self, Self::Error> {
let ext = match ext.extension() {
Some(ext) => ext,
None => {
@ -147,3 +141,19 @@ impl TryFrom<&str> for CompressionFormat {
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 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<()> {
if !path.exists() {
println!(