use std::{ffi::OsStr, fs, io::Write, path::PathBuf}; use colored::Colorize; use crate::compressors::{ Entry, Compressor, TarCompressor, ZipCompressor }; use crate::decompressors::{ Decompressor, TarDecompressor, ZipDecompressor, NifflerDecompressor, DecompressionResult }; use crate::extension::{ Extension, CompressionFormat }; use crate::cli::{Command, CommandKind}; use crate::error::{self, OuchResult}; use crate::file::File; use crate::utils; pub struct Evaluator { // verbosity: Verbosity } impl Evaluator { fn get_compressor( file: &File, ) -> error::OuchResult<(Option>, Box)> { if file.extension.is_none() { // This block *should* be unreachable eprintln!( "{}: reached Evaluator::get_decompressor without known extension.", "internal error".red() ); return Err(error::Error::InvalidInput); } let extension = file.extension.clone().unwrap(); // Supported first compressors: // .tar and .zip let first_compressor: Option> = match extension.first_ext { Some(ext) => match ext { CompressionFormat::Tar => Some(Box::new(TarCompressor {})), CompressionFormat::Zip => Some(Box::new(ZipCompressor {})), // _other => Some(Box::new(NifflerCompressor {})), _other => { todo!(); } }, None => None, }; // Supported second compressors: // any let second_compressor: Box = match extension.second_ext { CompressionFormat::Tar => Box::new(TarCompressor {}), CompressionFormat::Zip => Box::new(ZipCompressor {}), _other => todo!() // }; Ok((first_compressor, second_compressor)) } fn get_decompressor( file: &File, ) -> error::OuchResult<(Option>, Box)> { if file.extension.is_none() { // This block *should* be unreachable eprintln!( "{}: reached Evaluator::get_decompressor without known extension.", "internal error".red() ); return Err(error::Error::InvalidInput); } let extension = file.extension.clone().unwrap(); let second_decompressor: Box = match extension.second_ext { CompressionFormat::Tar => Box::new(TarDecompressor {}), CompressionFormat::Zip => Box::new(ZipDecompressor {}), CompressionFormat::Gzip | CompressionFormat::Lzma | CompressionFormat::Bzip => { Box::new(NifflerDecompressor {}) } }; let first_decompressor: Option> = match extension.first_ext { Some(ext) => match ext { CompressionFormat::Tar => Some(Box::new(TarDecompressor {})), CompressionFormat::Zip => Some(Box::new(ZipDecompressor {})), _other => None, }, None => None, }; Ok((first_decompressor, second_decompressor)) } // todo: move this folder into decompressors/ later on fn decompress_file_in_memory( bytes: Vec, file_path: PathBuf, decompressor: Option>, output_file: &Option, extension: Option, ) -> OuchResult<()> { let output_file_path = utils::get_destination_path(output_file); let mut filename = file_path .file_stem() .unwrap_or(output_file_path.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"); } let filename = PathBuf::from(filename); 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_path.join(filename))?; f.write_all(&bytes)?; return Ok(()); } let file = File { path: filename, contents_in_memory: Some(bytes), extension, }; let decompressor = decompressor.unwrap(); // If there is a decompressor to use, we'll create a file in-memory and decompress it let decompression_result = decompressor.decompress(file, output_file)?; if let DecompressionResult::FileInMemory(_) = decompression_result { // Should not be reachable. unreachable!(); } Ok(()) } fn compress_files(files: Vec, mut output: File) -> error::OuchResult<()> { let (first_compressor, second_compressor) = Self::get_compressor(&output)?; let output_path = output.path.clone(); let bytes = match first_compressor { Some(first_compressor) => { let mut entry = Entry::Files(files); let bytes = first_compressor.compress(entry)?; output.contents_in_memory = Some(bytes); entry = Entry::InMemory(output); second_compressor.compress(entry)? }, None => { let entry = Entry::Files(files); second_compressor.compress(entry)? } }; println!("{}: writing to {:?}. ({} bytes)", "info".yellow(), &output_path, bytes.len()); fs::write( output_path, bytes)?; Ok(()) } fn decompress_file(file: File, output: &Option) -> error::OuchResult<()> { // let output_file = &command.output; let (first_decompressor, second_decompressor) = Self::get_decompressor(&file)?; let file_path = file.path.clone(); let extension = file.extension.clone(); let decompression_result = second_decompressor.decompress(file, output)?; 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_path, first_decompressor, output, extension, )?; } 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(()) } pub fn evaluate(command: Command) -> error::OuchResult<()> { let output = command.output.clone(); match command.kind { CommandKind::Compression(files_to_compress) => { // Safe to unwrap since output is mandatory for compression let output = output.unwrap(); Self::compress_files(files_to_compress, output)?; } CommandKind::Decompression(files_to_decompress) => { for file in files_to_decompress { Self::decompress_file(file, &output)?; } } } Ok(()) } }