diff --git a/src/compressors/bzip.rs b/src/compressors/bzip.rs new file mode 100644 index 0000000..f66ef8d --- /dev/null +++ b/src/compressors/bzip.rs @@ -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, format: CompressionFormat) -> OuchResult> { +// 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, format: CompressionFormat) -> OuchResult> { + 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> { + // 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> { + 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) -> Box { + 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> { + match from { + Entry::Files(files) => Ok( + Self::compress_files(files, CompressionFormat::Bzip)? + ), + Entry::InMemory(file) => Ok( + Self::compress_file_in_memory(file)? + ), + } + } +} \ No newline at end of file diff --git a/src/compressors/mod.rs b/src/compressors/mod.rs index c9c9d13..fd1e83c 100644 --- a/src/compressors/mod.rs +++ b/src/compressors/mod.rs @@ -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; diff --git a/src/compressors/unified.rs b/src/compressors/unified.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/decompressors/tomemory.rs b/src/decompressors/tomemory.rs index 919cbb2..faf2a57 100644 --- a/src/decompressors/tomemory.rs +++ b/src/decompressors/tomemory.rs @@ -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 OuchResult> { let file = std::fs::read(from)?; @@ -62,18 +62,18 @@ impl UnifiedDecompressor { impl Decompressor for GzipDecompressor { fn decompress(&self, from: File, into: &Option) -> OuchResult { - UnifiedDecompressor::decompress(from, CompressionFormat::Gzip, into) + DecompressorToMemory::decompress(from, CompressionFormat::Gzip, into) } } impl Decompressor for BzipDecompressor { fn decompress(&self, from: File, into: &Option) -> OuchResult { - UnifiedDecompressor::decompress(from, CompressionFormat::Bzip, into) + DecompressorToMemory::decompress(from, CompressionFormat::Bzip, into) } } impl Decompressor for LzmaDecompressor { fn decompress(&self, from: File, into: &Option) -> OuchResult { - UnifiedDecompressor::decompress(from, CompressionFormat::Lzma, into) + DecompressorToMemory::decompress(from, CompressionFormat::Lzma, into) } } \ No newline at end of file diff --git a/src/evaluator.rs b/src/evaluator.rs index b0849f1..963448b 100644 --- a/src/evaluator.rs +++ b/src/evaluator.rs @@ -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 = 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!() }; diff --git a/src/extension.rs b/src/extension.rs index ee4f86d..49915e1 100644 --- a/src/extension.rs +++ b/src/extension.rs @@ -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, - 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 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 { - 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 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 { - 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", + } + ) + } +} diff --git a/src/utils.rs b/src/utils.rs index 82babb9..bb90769 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -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 + '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!(