From bb93e465354a8d61e2a2dd704a024d5324cd1f80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Tue, 3 Aug 2021 21:18:22 -0300 Subject: [PATCH] Reworked compression Now works with multiple formats, like archive.zip.gz.lz.gz.gz2.xz Now with minimum in-memory copying, compressing and decompressing (with exception to .zip, due to the format limitations) is all done directly into the final destination file. --- src/archive/tar.rs | 41 +++++++-- src/archive/zip.rs | 56 +++++++++++- src/cli.rs | 8 +- src/commands.rs | 146 +++++++++++++++---------------- src/compressors/bzip.rs | 59 ------------- src/compressors/compressor.rs | 12 --- src/compressors/gzip.rs | 64 -------------- src/compressors/lzma.rs | 64 -------------- src/compressors/mod.rs | 14 --- src/compressors/tar.rs | 53 ----------- src/compressors/zip.rs | 94 -------------------- src/extension.rs | 71 ++------------- src/file.rs | 26 ------ src/lib.rs | 2 - src/utils.rs | 34 +------ tests/compress_and_decompress.rs | 17 ++-- 16 files changed, 180 insertions(+), 581 deletions(-) delete mode 100644 src/compressors/bzip.rs delete mode 100644 src/compressors/compressor.rs delete mode 100644 src/compressors/gzip.rs delete mode 100644 src/compressors/lzma.rs delete mode 100644 src/compressors/mod.rs delete mode 100644 src/compressors/tar.rs delete mode 100644 src/compressors/zip.rs delete mode 100644 src/file.rs diff --git a/src/archive/tar.rs b/src/archive/tar.rs index 5c3759c..5cd750e 100644 --- a/src/archive/tar.rs +++ b/src/archive/tar.rs @@ -1,25 +1,20 @@ use std::{ - io::Read, + env, fs, + io::prelude::*, path::{Path, PathBuf}, }; use tar; use utils::colors; +use walkdir::WalkDir; use crate::{oof, utils}; + pub fn unpack_archive( reader: Box, output_folder: &Path, flags: &oof::Flags, ) -> crate::Result> { - // TODO: move this printing to the caller. - // println!( - // "{}[INFO]{} attempting to decompress {:?}", - // colors::blue(), - // colors::reset(), - // &input_path - // ); - let mut archive = tar::Archive::new(reader); let mut files_unpacked = vec![]; @@ -47,3 +42,31 @@ pub fn unpack_archive( Ok(files_unpacked) } + +pub fn build_archive_from_paths(input_filenames: &[PathBuf], writer: W) -> crate::Result +where + W: Write, +{ + let mut builder = tar::Builder::new(writer); + + for filename in input_filenames { + let previous_location = utils::cd_into_same_dir_as(filename)?; + + // Safe unwrap, input shall be treated before + let filename = filename.file_name().unwrap(); + + for entry in WalkDir::new(&filename) { + let entry = entry?; + let path = entry.path(); + + println!("Compressing '{}'.", utils::to_utf(path)); + if !path.is_dir() { + let mut file = fs::File::open(path)?; + builder.append_file(path, &mut file)?; + } + } + env::set_current_dir(previous_location)?; + } + + Ok(builder.into_inner()?) +} diff --git a/src/archive/zip.rs b/src/archive/zip.rs index 8ee2566..1349c46 100644 --- a/src/archive/zip.rs +++ b/src/archive/zip.rs @@ -1,9 +1,10 @@ use std::{ - fs, - io::{self, Read, Seek}, + env, fs, + io::{self, prelude::*}, path::{Path, PathBuf}, }; +use walkdir::WalkDir; use zip::{self, read::ZipFile, ZipArchive}; use crate::{ @@ -90,3 +91,54 @@ where Ok(unpacked_files) } + +pub fn build_archive_from_paths(input_filenames: &[PathBuf], writer: W) -> crate::Result +where + W: Write + Seek, +{ + let mut writer = zip::ZipWriter::new(writer); + let options = zip::write::FileOptions::default(); + + // Vec of any filename that failed the UTF-8 check + let invalid_unicode_filenames: Vec = input_filenames + .iter() + .map(|path| (path, path.to_str())) + .filter(|(_, x)| x.is_none()) + .map(|(a, _)| a.to_path_buf()) + .collect(); + + if !invalid_unicode_filenames.is_empty() { + panic!( + "invalid unicode filenames found, cannot be supported by Zip:\n {:#?}", + invalid_unicode_filenames + ); + } + + for filename in input_filenames { + let previous_location = utils::cd_into_same_dir_as(filename)?; + + // Safe unwrap, input shall be treated before + let filename = filename.file_name().unwrap(); + + for entry in WalkDir::new(filename) { + let entry = entry?; + let path = &entry.path(); + + println!("Compressing '{}'.", utils::to_utf(path)); + if path.is_dir() { + continue; + } + + writer.start_file(path.to_str().unwrap().to_owned(), options)?; + // TODO: check if isn't there a function that already does this for us...... + // TODO: better error messages + let file_bytes = fs::read(entry.path())?; + writer.write_all(&*file_bytes)?; + } + + env::set_current_dir(previous_location)?; + } + + let bytes = writer.finish()?; + Ok(bytes) +} diff --git a/src/cli.rs b/src/cli.rs index f59255f..9ea21e9 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -14,7 +14,7 @@ pub enum Command { /// Files to be compressed Compress { files: Vec, - compressed_output_path: PathBuf, + output_path: PathBuf, }, /// Files to be decompressed and their extensions Decompress { @@ -103,9 +103,9 @@ pub fn parse_args_from(mut args: Vec) -> crate::Result { } // Safety: we checked that args.len() >= 2 - let compressed_output_path = files.pop().unwrap(); + let output_path = files.pop().unwrap(); - let command = Command::Compress { files, compressed_output_path }; + let command = Command::Compress { files, output_path }; ParsedArgs { command, flags } }, // Defaults to decompression when there is no subcommand @@ -163,7 +163,7 @@ mod tests { }); assert_eq!(test_cli("compress foo bar baz.zip").unwrap().command, Command::Compress { files: vec!["foo".into(), "bar".into()], - compressed_output_path: "baz.zip".into() + output_path: "baz.zip".into() }); assert_eq!(test_cli("compress").unwrap_err(), crate::Error::MissingArgumentsForCompression); } diff --git a/src/commands.rs b/src/commands.rs index e8a0a5b..119d207 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,29 +1,31 @@ use std::{ fs, - io::{self, BufReader, Read}, + io::{self, BufReader, BufWriter, Read, Write}, path::{Path, PathBuf}, }; use utils::colors; use crate::{ + archive, cli::Command, - compressors::{ - BzipCompressor, Compressor, Entry, GzipCompressor, LzmaCompressor, TarCompressor, - ZipCompressor, - }, extension::{ self, CompressionFormat::{self, *}, }, - file, oof, utils, + oof, utils, utils::to_utf, }; pub fn run(command: Command, flags: &oof::Flags) -> crate::Result<()> { match command { - Command::Compress { files, compressed_output_path } => { - compress_files(files, &compressed_output_path, flags)? + Command::Compress { files, output_path } => { + let formats = extension::extensions_from_path(&output_path); + let output_file = fs::File::create(&output_path)?; + let compression_result = compress_files(files, formats, output_file, flags); + if let Err(_err) = compression_result { + fs::remove_file(&output_path).unwrap(); + } }, Command::Decompress { files, output_folder } => { let mut output_paths = vec![]; @@ -67,81 +69,73 @@ pub fn run(command: Command, flags: &oof::Flags) -> crate::Result<()> { Ok(()) } -type BoxedCompressor = Box; - -fn get_compressor(file: &file::File) -> crate::Result<(Option, BoxedCompressor)> { - let extension = match &file.extension { - Some(extension) => extension, - None => { - // This is reached when the output file given does not have an extension or has an unsupported one - return Err(crate::Error::MissingExtensionError(file.path.to_path_buf())); - }, - }; - - // 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)), - CompressionFormat::Bzip => Some(Box::new(BzipCompressor)), - CompressionFormat::Gzip => Some(Box::new(GzipCompressor)), - CompressionFormat::Lzma => Some(Box::new(LzmaCompressor)), - }, - None => None, - }; - - // Supported second compressors: - // any - let second_compressor: Box = match extension.second_ext { - CompressionFormat::Tar => Box::new(TarCompressor), - CompressionFormat::Zip => Box::new(ZipCompressor), - CompressionFormat::Bzip => Box::new(BzipCompressor), - CompressionFormat::Gzip => Box::new(GzipCompressor), - CompressionFormat::Lzma => Box::new(LzmaCompressor), - }; - - Ok((first_compressor, second_compressor)) -} - fn compress_files( files: Vec, - output_path: &Path, - flags: &oof::Flags, + formats: Vec, + output_file: fs::File, + _flags: &oof::Flags, ) -> crate::Result<()> { - let mut output = file::File::from(output_path)?; + let file_writer = BufWriter::new(output_file); - let (first_compressor, second_compressor) = get_compressor(&output)?; + if formats.len() == 1 { + let build_archive_from_paths = match formats[0] { + Tar => archive::tar::build_archive_from_paths, + Zip => archive::zip::build_archive_from_paths, + _ => unreachable!(), + }; - if output_path.exists() && !utils::permission_for_overwriting(output_path, flags)? { - // The user does not want to overwrite the file - return Ok(()); + let mut bufwriter = build_archive_from_paths(&files, file_writer)?; + bufwriter.flush()?; + } else { + let mut writer: Box = Box::new(file_writer); + + // Grab previous encoder and wrap it inside of a new one + let chain_writer_encoder = |format: &CompressionFormat, encoder: Box| { + let encoder: Box = match format { + Gzip => Box::new(flate2::write::GzEncoder::new(encoder, Default::default())), + Bzip => Box::new(bzip2::write::BzEncoder::new(encoder, Default::default())), + Lzma => Box::new(xz2::write::XzEncoder::new(encoder, 6)), + _ => unreachable!(), + }; + encoder + }; + + for format in formats.iter().skip(1).rev() { + writer = chain_writer_encoder(format, writer); + } + + match formats[0] { + Gzip | Bzip | Lzma => { + writer = chain_writer_encoder(&formats[0], writer); + let mut reader = fs::File::open(&files[0]).unwrap(); + io::copy(&mut reader, &mut writer)?; + }, + Tar => { + let mut writer = archive::tar::build_archive_from_paths(&files, writer)?; + writer.flush()?; + }, + Zip => { + eprintln!( + "{yellow}Warning:{reset}", + yellow = colors::yellow(), + reset = colors::reset() + ); + eprintln!("\tCompressing .zip entirely in memory."); + eprintln!("\tIf the file is too big, your pc might freeze!"); + eprintln!( + "\tThis is a limitation for formats like '{}'.", + formats.iter().map(|format| format.to_string()).collect::() + ); + eprintln!("\tThe design of .zip makes it impossible to compress via stream."); + + let mut vec_buffer = io::Cursor::new(vec![]); + archive::zip::build_archive_from_paths(&files, &mut vec_buffer)?; + let vec_buffer = vec_buffer.into_inner(); + io::copy(&mut vec_buffer.as_slice(), &mut writer)?; + }, + } } - 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!( - "{}[INFO]{} writing to {:?}. ({})", - colors::yellow(), - colors::reset(), - output_path, - utils::Bytes::new(bytes.len() as u64) - ); - fs::write(output_path, bytes)?; - Ok(()) } diff --git a/src/compressors/bzip.rs b/src/compressors/bzip.rs deleted file mode 100644 index 66a2ca4..0000000 --- a/src/compressors/bzip.rs +++ /dev/null @@ -1,59 +0,0 @@ -use std::{fs, io::Write, path::PathBuf}; - -use utils::colors; - -use super::{Compressor, Entry}; -use crate::{extension::CompressionFormat, file::File, utils}; - -pub struct BzipCompressor; - -impl BzipCompressor { - fn compress_files(files: Vec, format: CompressionFormat) -> crate::Result> { - utils::check_for_multiple_files(&files, &format)?; - let path = &files[0]; - utils::ensure_exists(path)?; - let contents = { - let bytes = fs::read(path)?; - Self::compress_bytes(&*bytes)? - }; - - println!( - "{}[INFO]{} compressed {:?} into memory ({})", - colors::yellow(), - colors::reset(), - &path, - utils::Bytes::new(contents.len() as u64) - ); - - Ok(contents) - } - - fn compress_file_in_memory(file: File) -> crate::Result> { - // Ensure that our file has in-memory content - let bytes = match file.contents_in_memory { - Some(bytes) => bytes, - None => { - return Err(crate::Error::InternalError); - }, - }; - - Self::compress_bytes(&*bytes) - } - - fn compress_bytes(bytes: &[u8]) -> crate::Result> { - 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 -impl Compressor for BzipCompressor { - fn compress(&self, from: Entry) -> crate::Result> { - match from { - Entry::Files(files) => Ok(Self::compress_files(files, CompressionFormat::Bzip)?), - Entry::InMemory(file) => Ok(Self::compress_file_in_memory(file)?), - } - } -} diff --git a/src/compressors/compressor.rs b/src/compressors/compressor.rs deleted file mode 100644 index 7773dce..0000000 --- a/src/compressors/compressor.rs +++ /dev/null @@ -1,12 +0,0 @@ -use std::path::PathBuf; - -use crate::file::File; - -pub enum Entry<'a> { - Files(Vec), - InMemory(File<'a>), -} - -pub trait Compressor { - fn compress(&self, from: Entry) -> crate::Result>; -} diff --git a/src/compressors/gzip.rs b/src/compressors/gzip.rs deleted file mode 100644 index 398ed58..0000000 --- a/src/compressors/gzip.rs +++ /dev/null @@ -1,64 +0,0 @@ -use std::{fs, io::Write, path::PathBuf}; - -use utils::colors; - -use super::{Compressor, Entry}; -use crate::{extension::CompressionFormat, file::File, utils}; - -pub struct GzipCompressor; - -impl GzipCompressor { - pub fn compress_files( - files: Vec, - format: CompressionFormat, - ) -> crate::Result> { - utils::check_for_multiple_files(&files, &format)?; - - let path = &files[0]; - utils::ensure_exists(path)?; - - let bytes = { - let bytes = fs::read(path)?; - Self::compress_bytes(bytes)? - }; - - println!( - "{}[INFO]{} compressed {:?} into memory ({})", - colors::yellow(), - colors::reset(), - &path, - utils::Bytes::new(bytes.len() as u64) - ); - - Ok(bytes) - } - - pub fn compress_file_in_memory(file: File) -> crate::Result> { - let file_contents = match file.contents_in_memory { - Some(bytes) => bytes, - None => { - unreachable!(); - }, - }; - - Self::compress_bytes(file_contents) - } - - pub fn compress_bytes(bytes_to_compress: Vec) -> crate::Result> { - let buffer = vec![]; - let mut encoder = flate2::write::GzEncoder::new(buffer, flate2::Compression::default()); - encoder.write_all(&*bytes_to_compress)?; - - Ok(encoder.finish()?) - } -} - -impl Compressor for GzipCompressor { - fn compress(&self, from: Entry) -> crate::Result> { - let format = CompressionFormat::Gzip; - match from { - Entry::Files(files) => Ok(Self::compress_files(files, format)?), - Entry::InMemory(file) => Ok(Self::compress_file_in_memory(file)?), - } - } -} diff --git a/src/compressors/lzma.rs b/src/compressors/lzma.rs deleted file mode 100644 index 45d36b2..0000000 --- a/src/compressors/lzma.rs +++ /dev/null @@ -1,64 +0,0 @@ -use std::{fs, io::Write, path::PathBuf}; - -use utils::colors; - -use super::{Compressor, Entry}; -use crate::{extension::CompressionFormat, file::File, utils}; - -pub struct LzmaCompressor; - -impl LzmaCompressor { - pub fn compress_files( - files: Vec, - format: CompressionFormat, - ) -> crate::Result> { - utils::check_for_multiple_files(&files, &format)?; - - let path = &files[0]; - utils::ensure_exists(path)?; - - let bytes = { - let bytes = fs::read(path)?; - Self::compress_bytes(bytes)? - }; - - println!( - "{}[INFO]{} compressed {:?} into memory ({})", - colors::yellow(), - colors::reset(), - &path, - utils::Bytes::new(bytes.len() as u64) - ); - - Ok(bytes) - } - - pub fn compress_file_in_memory(file: File) -> crate::Result> { - let file_contents = match file.contents_in_memory { - Some(bytes) => bytes, - None => { - unreachable!(); - }, - }; - - Self::compress_bytes(file_contents) - } - - pub fn compress_bytes(bytes_to_compress: Vec) -> crate::Result> { - let buffer = vec![]; - let mut encoder = xz2::write::XzEncoder::new(buffer, 6); - encoder.write_all(&*bytes_to_compress)?; - - Ok(encoder.finish()?) - } -} - -impl Compressor for LzmaCompressor { - fn compress(&self, from: Entry) -> crate::Result> { - let format = CompressionFormat::Lzma; - match from { - Entry::Files(files) => Self::compress_files(files, format), - Entry::InMemory(file) => Self::compress_file_in_memory(file), - } - } -} diff --git a/src/compressors/mod.rs b/src/compressors/mod.rs deleted file mode 100644 index ecfed8b..0000000 --- a/src/compressors/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! This module contains the Compressor trait and an implementor for each format. -mod bzip; -mod compressor; -mod gzip; -mod lzma; -mod tar; -mod zip; - -pub use compressor::Compressor; - -pub use self::{ - bzip::BzipCompressor, compressor::Entry, gzip::GzipCompressor, lzma::LzmaCompressor, - tar::TarCompressor, zip::ZipCompressor, -}; diff --git a/src/compressors/tar.rs b/src/compressors/tar.rs deleted file mode 100644 index 60d891d..0000000 --- a/src/compressors/tar.rs +++ /dev/null @@ -1,53 +0,0 @@ -use std::{env, fs, path::PathBuf}; - -use tar::Builder; -use utils::colors; -use walkdir::WalkDir; - -use super::compressor::Entry; -use crate::{compressors::Compressor, file::File, utils}; - -pub struct TarCompressor; - -impl TarCompressor { - // TODO: implement this - fn make_archive_from_memory(_input: File) -> crate::Result> { - println!( - "{}[ERROR]{} .tar.tar and .zip.tar is currently unimplemented.", - colors::red(), - colors::reset() - ); - Err(crate::Error::InvalidZipArchive("")) - } - - fn make_archive_from_files(input_filenames: Vec) -> crate::Result> { - let buf = Vec::new(); - let mut b = Builder::new(buf); - - for filename in input_filenames { - let previous_location = utils::change_dir_and_return_parent(&filename)?; - let filename = filename.file_name().unwrap(); - for entry in WalkDir::new(&filename) { - let entry = entry?; - let path = entry.path(); - println!("Compressing {:?}", path); - if path.is_dir() { - continue; - } - b.append_file(path, &mut fs::File::open(path)?)?; - } - env::set_current_dir(previous_location)?; - } - - Ok(b.into_inner()?) - } -} - -impl Compressor for TarCompressor { - fn compress(&self, from: Entry) -> crate::Result> { - match from { - Entry::Files(filenames) => Self::make_archive_from_files(filenames), - Entry::InMemory(file) => Self::make_archive_from_memory(file), - } - } -} diff --git a/src/compressors/zip.rs b/src/compressors/zip.rs deleted file mode 100644 index cb5a717..0000000 --- a/src/compressors/zip.rs +++ /dev/null @@ -1,94 +0,0 @@ -use std::{ - io::{Cursor, Write}, - path::PathBuf, -}; - -use walkdir::WalkDir; - -use super::compressor::Entry; -use crate::{compressors::Compressor, file::File, utils}; - -pub struct ZipCompressor; - -impl ZipCompressor { - // TODO: this function does not seem to be working correctly ;/ - fn make_archive_from_memory(input: File) -> crate::Result> { - let buffer = vec![]; - let mut writer = zip::ZipWriter::new(std::io::Cursor::new(buffer)); - - let inner_file_path: Box = input - .path - .file_stem() - .ok_or( - // TODO: Is this reachable? - crate::Error::InvalidInput, - )? - .to_string_lossy() - .into(); - - let options = - zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Deflated); - - writer.start_file(inner_file_path, options)?; - - let input_bytes = match input.contents_in_memory { - Some(bytes) => bytes, - None => { - // TODO: error description, although this block should not be - // reachable - return Err(crate::Error::InvalidInput); - }, - }; - - writer.write_all(&*input_bytes)?; - - let bytes = writer.finish().unwrap(); - - Ok(bytes.into_inner()) - } - - fn make_archive_from_files(input_filenames: Vec) -> crate::Result> { - let buffer = vec![]; - let mut writer = zip::ZipWriter::new(Cursor::new(buffer)); - - let options = - zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Deflated); - - for filename in input_filenames { - let previous_location = utils::change_dir_and_return_parent(&filename)?; - let filename = filename - .file_name() - // Safe unwrap since the function call above would fail in scenarios - // where this unwrap would panic - .unwrap(); - - for entry in WalkDir::new(filename) { - let entry = entry?; - let entry_path = &entry.path(); - if entry_path.is_dir() { - continue; - } - - writer.start_file(entry_path.to_string_lossy(), options)?; - println!("Compressing {:?}", entry_path); - let file_bytes = std::fs::read(entry.path())?; - writer.write_all(&*file_bytes)?; - } - - std::env::set_current_dir(previous_location)?; - } - - let bytes = writer.finish()?; - - Ok(bytes.into_inner()) - } -} - -impl Compressor for ZipCompressor { - fn compress(&self, from: Entry) -> crate::Result> { - match from { - Entry::Files(filenames) => Ok(Self::make_archive_from_files(filenames)?), - Entry::InMemory(file) => Ok(Self::make_archive_from_memory(file)?), - } - } -} diff --git a/src/extension.rs b/src/extension.rs index 4f442fb..e485a45 100644 --- a/src/extension.rs +++ b/src/extension.rs @@ -1,71 +1,7 @@ -use std::{ - ffi::OsStr, - fmt, - path::{Path, PathBuf}, -}; +use std::{fmt, path::Path}; use CompressionFormat::*; -use crate::utils; - -/// Represents the extension of a file, but only really caring about -/// compression formats (and .tar). -/// Ex.: Extension::new("file.tar.gz") == Extension { first_ext: Some(Tar), second_ext: Gzip } -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Extension { - pub first_ext: Option, - pub second_ext: CompressionFormat, -} - -pub fn get_extension_from_filename(file_name: &OsStr) -> Option<(&OsStr, &OsStr)> { - let path = Path::new(file_name); - - let ext = path.extension()?; - - let previous_extension = path.file_stem().and_then(get_extension_from_filename); - - if let Some((_, prev)) = previous_extension { - Some((prev, ext)) - } else { - Some((OsStr::new(""), ext)) - } -} - -impl Extension { - pub fn from(file_name: &OsStr) -> crate::Result { - let compression_format_from = |ext: &OsStr| match ext { - _ if ext == "zip" => Ok(Zip), - _ if ext == "tar" => Ok(Tar), - _ if ext == "gz" => Ok(Gzip), - _ if ext == "bz" || ext == "bz2" => Ok(Bzip), - _ if ext == "xz" || ext == "lz" || ext == "lzma" => Ok(Lzma), - other => Err(crate::Error::UnknownExtensionError(utils::to_utf(other))), - }; - - let (first_ext, second_ext) = match get_extension_from_filename(file_name) { - Some(extension_tuple) => match extension_tuple { - (os_str, snd) if os_str.is_empty() => (None, snd), - (fst, snd) => (Some(fst), snd), - }, - None => return Err(crate::Error::MissingExtensionError(PathBuf::from(file_name))), - }; - - let (first_ext, second_ext) = match (first_ext, second_ext) { - (None, snd) => { - let ext = compression_format_from(snd)?; - (None, ext) - }, - (Some(fst), snd) => { - let snd = compression_format_from(snd)?; - let fst = compression_format_from(fst).ok(); - (fst, snd) - }, - }; - - Ok(Self { first_ext, second_ext }) - } -} - #[derive(Clone, PartialEq, Eq, Debug)] /// Accepted extensions for input and output pub enum CompressionFormat { @@ -120,3 +56,8 @@ pub fn separate_known_extensions_from_name(mut path: &Path) -> (&Path, Vec Vec { + let (_, extensions) = separate_known_extensions_from_name(path); + extensions +} diff --git a/src/file.rs b/src/file.rs deleted file mode 100644 index c14d2d8..0000000 --- a/src/file.rs +++ /dev/null @@ -1,26 +0,0 @@ -use std::path::Path; - -use crate::extension::Extension; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct File<'a> { - /// File's (relative) path - pub path: &'a Path, - /// The bytes that compose the file. - /// Only used when the whole file is kept in-memory - pub contents_in_memory: Option>, - /// Note: extension here might be a misleading name since - /// we don't really care about any extension other than supported compression ones. - /// - /// So, for example, if a file has pathname "image.jpeg", it does have a JPEG extension but will - /// be represented as a None over here since that's not an extension we're particularly interested in - pub extension: Option, -} - -impl<'a> File<'a> { - pub fn from(path: &'a Path) -> crate::Result { - let extension = Extension::from(path.as_ref()).ok(); - - Ok(File { path, contents_in_memory: None, extension }) - } -} diff --git a/src/lib.rs b/src/lib.rs index 3ea998f..4116bb1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,11 +5,9 @@ pub mod oof; // Private modules pub mod archive; -mod compressors; mod dialogs; mod error; mod extension; -mod file; mod utils; use dialogs::Confirmation; diff --git a/src/utils.rs b/src/utils.rs index 9d0274f..1b67cdb 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -5,7 +5,7 @@ use std::{ path::{Path, PathBuf}, }; -use crate::{extension::CompressionFormat, oof, OVERWRITE_CONFIRMATION}; +use crate::{oof, OVERWRITE_CONFIRMATION}; #[macro_export] #[cfg(debug_assertions)] @@ -23,34 +23,6 @@ macro_rules! debug { }; } -pub fn ensure_exists(path: impl AsRef) -> crate::Result<()> { - let exists = path.as_ref().exists(); - if !exists { - return Err(crate::Error::FileNotFound(PathBuf::from(path.as_ref()))); - } - Ok(()) -} - -pub fn check_for_multiple_files( - files: &[PathBuf], - format: &CompressionFormat, -) -> crate::Result<()> { - if files.len() != 1 { - eprintln!( - "{}[ERROR]{} cannot compress multiple files directly to {:#?}.\n\ - Try using an intermediate archival method such as Tar.\n\ - Example: filename.tar{}", - colors::red(), - colors::reset(), - format, - format - ); - return Err(crate::Error::InvalidInput); - } - - Ok(()) -} - pub fn create_dir_if_non_existent(path: &Path) -> crate::Result<()> { if !path.exists() { println!( @@ -70,7 +42,7 @@ pub fn create_dir_if_non_existent(path: &Path) -> crate::Result<()> { Ok(()) } -pub fn change_dir_and_return_parent(filename: &Path) -> crate::Result { +pub fn cd_into_same_dir_as(filename: &Path) -> crate::Result { let previous_location = env::current_dir()?; let parent = if let Some(parent) = filename.parent() { @@ -79,6 +51,8 @@ pub fn change_dir_and_return_parent(filename: &Path) -> crate::Result { return Err(crate::Error::CompressingRootFolder); }; + // TODO: fix this error variant, as it is not the only possible error that can + // come out of this operation env::set_current_dir(parent).ok().ok_or(crate::Error::CompressingRootFolder)?; Ok(previous_location) diff --git a/tests/compress_and_decompress.rs b/tests/compress_and_decompress.rs index 0f3cf32..aadf255 100644 --- a/tests/compress_and_decompress.rs +++ b/tests/compress_and_decompress.rs @@ -15,17 +15,20 @@ fn test_each_format() { test_compression_and_decompression("tar"); test_compression_and_decompression("tar.gz"); test_compression_and_decompression("tar.bz"); - // test_compression_and_decompression("tar.bz2"); - // test_compression_and_decompression("tar.xz"); + test_compression_and_decompression("tar.bz2"); + test_compression_and_decompression("tar.xz"); test_compression_and_decompression("tar.lz"); - // test_compression_and_decompression("tar.lzma"); + test_compression_and_decompression("tar.lzma"); test_compression_and_decompression("zip"); test_compression_and_decompression("zip.gz"); test_compression_and_decompression("zip.bz"); - // test_compression_and_decompression("zip.bz2"); - // test_compression_and_decompression("zip.xz"); + test_compression_and_decompression("zip.bz2"); + test_compression_and_decompression("zip.xz"); test_compression_and_decompression("zip.lz"); - // test_compression_and_decompression("zip.lzma"); + test_compression_and_decompression("zip.lzma"); + + // Why not + test_compression_and_decompression("tar.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.lz.lz.lz.lz.lz.lz.lz.lz.lz.lz.bz.bz.bz.bz.bz.bz.bz"); } type FileContent = Vec; @@ -92,7 +95,7 @@ fn compress_files(at: &Path, paths_to_compress: &[PathBuf], format: &str) -> Pat let command = Command::Compress { files: paths_to_compress.to_vec(), - compressed_output_path: archive_path.to_path_buf(), + output_path: archive_path.to_path_buf(), }; run(command, &oof::Flags::default()).expect("Failed to compress test dummy files");