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.
This commit is contained in:
João M. Bezerra 2021-08-03 21:18:22 -03:00
parent b969bda5a8
commit bb93e46535
16 changed files with 180 additions and 581 deletions

View File

@ -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<dyn Read>,
output_folder: &Path,
flags: &oof::Flags,
) -> crate::Result<Vec<PathBuf>> {
// 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<W>(input_filenames: &[PathBuf], writer: W) -> crate::Result<W>
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()?)
}

View File

@ -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<W>(input_filenames: &[PathBuf], writer: W) -> crate::Result<W>
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<PathBuf> = 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)
}

View File

@ -14,7 +14,7 @@ pub enum Command {
/// Files to be compressed
Compress {
files: Vec<PathBuf>,
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<OsString>) -> crate::Result<ParsedArgs> {
}
// 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);
}

View File

@ -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<dyn Compressor>;
fn get_compressor(file: &file::File) -> crate::Result<(Option<BoxedCompressor>, 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<Box<dyn Compressor>> = 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<dyn Compressor> = 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<PathBuf>,
output_path: &Path,
flags: &oof::Flags,
formats: Vec<CompressionFormat>,
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<dyn Write> = Box::new(file_writer);
// Grab previous encoder and wrap it inside of a new one
let chain_writer_encoder = |format: &CompressionFormat, encoder: Box<dyn Write>| {
let encoder: Box<dyn Write> = 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::<String>()
);
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(())
}

View File

@ -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<PathBuf>, format: CompressionFormat) -> crate::Result<Vec<u8>> {
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<Vec<u8>> {
// 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<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
impl Compressor for BzipCompressor {
fn compress(&self, from: Entry) -> crate::Result<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,12 +0,0 @@
use std::path::PathBuf;
use crate::file::File;
pub enum Entry<'a> {
Files(Vec<PathBuf>),
InMemory(File<'a>),
}
pub trait Compressor {
fn compress(&self, from: Entry) -> crate::Result<Vec<u8>>;
}

View File

@ -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<PathBuf>,
format: CompressionFormat,
) -> crate::Result<Vec<u8>> {
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<Vec<u8>> {
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<u8>) -> crate::Result<Vec<u8>> {
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<Vec<u8>> {
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)?),
}
}
}

View File

@ -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<PathBuf>,
format: CompressionFormat,
) -> crate::Result<Vec<u8>> {
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<Vec<u8>> {
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<u8>) -> crate::Result<Vec<u8>> {
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<Vec<u8>> {
let format = CompressionFormat::Lzma;
match from {
Entry::Files(files) => Self::compress_files(files, format),
Entry::InMemory(file) => Self::compress_file_in_memory(file),
}
}
}

View File

@ -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,
};

View File

@ -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<Vec<u8>> {
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<PathBuf>) -> crate::Result<Vec<u8>> {
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<Vec<u8>> {
match from {
Entry::Files(filenames) => Self::make_archive_from_files(filenames),
Entry::InMemory(file) => Self::make_archive_from_memory(file),
}
}
}

View File

@ -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<Vec<u8>> {
let buffer = vec![];
let mut writer = zip::ZipWriter::new(std::io::Cursor::new(buffer));
let inner_file_path: Box<str> = 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<PathBuf>) -> crate::Result<Vec<u8>> {
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<Vec<u8>> {
match from {
Entry::Files(filenames) => Ok(Self::make_archive_from_files(filenames)?),
Entry::InMemory(file) => Ok(Self::make_archive_from_memory(file)?),
}
}
}

View File

@ -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<CompressionFormat>,
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<Self> {
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<Compr
(path, extensions)
}
pub fn extensions_from_path(path: &Path) -> Vec<CompressionFormat> {
let (_, extensions) = separate_known_extensions_from_name(path);
extensions
}

View File

@ -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<Vec<u8>>,
/// 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<Extension>,
}
impl<'a> File<'a> {
pub fn from(path: &'a Path) -> crate::Result<Self> {
let extension = Extension::from(path.as_ref()).ok();
Ok(File { path, contents_in_memory: None, extension })
}
}

View File

@ -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;

View File

@ -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<Path>) -> 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<PathBuf> {
pub fn cd_into_same_dir_as(filename: &Path) -> crate::Result<PathBuf> {
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<PathBuf> {
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)

View File

@ -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<u8>;
@ -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");