mirror of
https://github.com/ouch-org/ouch.git
synced 2025-06-06 19:45:29 +00:00
Add Tar compression for in-memory buffers
This commit is contained in:
parent
fa2fb675de
commit
2c0f2b380c
@ -4,7 +4,7 @@ use clap::{Arg, Values};
|
|||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
|
|
||||||
use crate::error;
|
use crate::error;
|
||||||
use crate::extension::{Extension, CompressionFormat};
|
use crate::extension::Extension;
|
||||||
use crate::file::File;
|
use crate::file::File;
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Debug)]
|
#[derive(PartialEq, Eq, Debug)]
|
||||||
|
@ -2,22 +2,17 @@ use std::path::PathBuf;
|
|||||||
|
|
||||||
use crate::{error::OuchResult, file::File};
|
use crate::{error::OuchResult, file::File};
|
||||||
|
|
||||||
pub enum CompressionResult {
|
// pub enum CompressionResult {
|
||||||
ZipArchive(Vec<u8>),
|
// ZipArchive(Vec<u8>),
|
||||||
TarArchive(Vec<u8>),
|
// TarArchive(Vec<u8>),
|
||||||
FileInMemory(Vec<u8>)
|
// FileInMemory(Vec<u8>)
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub enum Entry {
|
||||||
|
Files(Vec<PathBuf>),
|
||||||
|
InMemory(File)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Compressor {
|
pub trait Compressor {
|
||||||
fn compress(&self, from: Vec<File>) -> OuchResult<CompressionResult>;
|
fn compress(&self, from: Vec<PathBuf>) -> OuchResult<Vec<u8>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
@ -1,5 +1,5 @@
|
|||||||
mod tar;
|
mod tar;
|
||||||
mod compressor;
|
mod compressor;
|
||||||
|
|
||||||
pub use compressor::{Compressor, CompressionResult};
|
pub use compressor::{Compressor};
|
||||||
pub use self::tar::TarCompressor;
|
pub use self::tar::TarCompressor;
|
@ -1,42 +1,62 @@
|
|||||||
use std::{fs::File, path::Path};
|
use std::{fs, path::PathBuf};
|
||||||
|
|
||||||
use tar::Builder;
|
use colored::Colorize;
|
||||||
|
use tar::{Builder, Header};
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
use crate::{decompressors::TarDecompressor, error::OuchResult};
|
use crate::{compressors::Compressor, error::{Error, OuchResult}, file::{self, File}};
|
||||||
use crate::compressors::Compressor;
|
|
||||||
use super::compressor::CompressionResult;
|
|
||||||
|
|
||||||
pub struct TarCompressor {}
|
pub struct TarCompressor {}
|
||||||
|
|
||||||
impl TarCompressor {
|
impl TarCompressor {
|
||||||
fn make_archive_in_memory(input_files: Vec<crate::file::File>) -> OuchResult<Vec<u8>> {
|
|
||||||
|
fn make_archive_from_memory(input: File) -> OuchResult<Vec<u8>> {
|
||||||
|
|
||||||
|
let contents = match input.contents {
|
||||||
|
Some(bytes) => bytes,
|
||||||
|
None => {
|
||||||
|
eprintln!("{}: reached TarCompressor::make_archive_from_memory without known content.", "internal error".red());
|
||||||
|
return Err(Error::InvalidInput);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut header = Header::new_gnu();
|
||||||
|
|
||||||
|
header.set_path(&input.path).unwrap();
|
||||||
|
header.set_size(contents.len() as u64);
|
||||||
|
header.set_cksum();
|
||||||
|
|
||||||
|
|
||||||
|
let mut b = Builder::new(Vec::new());
|
||||||
|
b.append_data(&mut header, &input.path, &*contents)?;
|
||||||
|
|
||||||
|
Ok(b.into_inner()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_archive_from_files(input_files: Vec<PathBuf>) -> OuchResult<Vec<u8>> {
|
||||||
|
|
||||||
let buf = Vec::new();
|
let buf = Vec::new();
|
||||||
let mut b = Builder::new(buf);
|
let mut b = Builder::new(buf);
|
||||||
|
|
||||||
for file in input_files {
|
for file in input_files {
|
||||||
for entry in WalkDir::new(&file.path) {
|
for entry in WalkDir::new(&file) {
|
||||||
let entry = entry.unwrap();
|
let entry = entry.unwrap();
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
if path.is_dir() {
|
if path.is_dir() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
b.append_file(path, &mut File::open(path).unwrap()).unwrap();
|
b.append_file(path, &mut fs::File::open(path).unwrap()).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Ok(b.into_inner()?)
|
Ok(b.into_inner()?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Compressor for TarCompressor {
|
impl Compressor for TarCompressor {
|
||||||
fn compress(&self, from: Vec<crate::file::File>) -> OuchResult<CompressionResult> {
|
fn compress(&self, from: Vec<PathBuf>) -> OuchResult<Vec<u8>> {
|
||||||
Ok(CompressionResult::TarArchive(
|
Ok(
|
||||||
TarCompressor::make_archive_in_memory(from)?
|
TarCompressor::make_archive_from_files(from)?
|
||||||
))
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fn compress(&self, from: Vec<File>, into: &Option<File>) -> OuchResult<CompressionResult>;
|
|
@ -2,23 +2,71 @@ use std::{ffi::OsStr, fs, io::Write, path::PathBuf};
|
|||||||
|
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
|
|
||||||
use crate::{decompressors::Decompressor, extension::{self, Extension}};
|
use crate::{compressors::TarCompressor, decompressors::TarDecompressor};
|
||||||
use crate::decompressors::TarDecompressor;
|
|
||||||
use crate::decompressors::ZipDecompressor;
|
use crate::decompressors::ZipDecompressor;
|
||||||
use crate::{
|
use crate::{
|
||||||
cli::{Command, CommandKind},
|
cli::{Command, CommandKind},
|
||||||
decompressors::{DecompressionResult, NifflerDecompressor},
|
decompressors::{
|
||||||
|
Decompressor,
|
||||||
|
DecompressionResult,
|
||||||
|
NifflerDecompressor
|
||||||
|
},
|
||||||
|
compressors::Compressor,
|
||||||
error::{self, OuchResult},
|
error::{self, OuchResult},
|
||||||
extension::CompressionFormat,
|
extension::{
|
||||||
|
Extension,
|
||||||
|
CompressionFormat,
|
||||||
|
},
|
||||||
file::File,
|
file::File,
|
||||||
utils,
|
utils,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
pub struct Evaluator {
|
pub struct Evaluator {
|
||||||
// verbosity: Verbosity
|
// verbosity: Verbosity
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Evaluator {
|
impl Evaluator {
|
||||||
|
fn get_compressor(
|
||||||
|
file: &File,
|
||||||
|
) -> error::OuchResult<(Option<Box<dyn Compressor>>, Box<dyn Compressor>)> {
|
||||||
|
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<Box<dyn Compressor>> = 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<dyn Compressor> = match extension.second_ext {
|
||||||
|
CompressionFormat::Tar => Box::new(TarCompressor {}),
|
||||||
|
_other => todo!()
|
||||||
|
//
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((first_compressor, second_compressor))
|
||||||
|
}
|
||||||
|
|
||||||
fn get_decompressor(
|
fn get_decompressor(
|
||||||
file: &File,
|
file: &File,
|
||||||
) -> error::OuchResult<(Option<Box<dyn Decompressor>>, Box<dyn Decompressor>)> {
|
) -> error::OuchResult<(Option<Box<dyn Decompressor>>, Box<dyn Decompressor>)> {
|
||||||
@ -31,7 +79,7 @@ impl Evaluator {
|
|||||||
return Err(error::Error::InvalidInput);
|
return Err(error::Error::InvalidInput);
|
||||||
}
|
}
|
||||||
let extension = file.extension.clone().unwrap();
|
let extension = file.extension.clone().unwrap();
|
||||||
|
|
||||||
let second_decompressor: Box<dyn Decompressor> = match extension.second_ext {
|
let second_decompressor: Box<dyn Decompressor> = match extension.second_ext {
|
||||||
CompressionFormat::Tar => Box::new(TarDecompressor {}),
|
CompressionFormat::Tar => Box::new(TarDecompressor {}),
|
||||||
|
|
||||||
@ -64,10 +112,11 @@ impl Evaluator {
|
|||||||
output_file: &Option<File>,
|
output_file: &Option<File>,
|
||||||
extension: Option<Extension>,
|
extension: Option<Extension>,
|
||||||
) -> OuchResult<()> {
|
) -> OuchResult<()> {
|
||||||
|
|
||||||
let output_file_path = utils::get_destination_path(output_file);
|
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());
|
let mut filename = file_path
|
||||||
|
.file_stem()
|
||||||
|
.unwrap_or(output_file_path.as_os_str());
|
||||||
if filename == "." {
|
if filename == "." {
|
||||||
// I believe this is only possible when the supplied inout has a name
|
// 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.
|
// of the sort `.tar` or `.zip' and no output has been supplied.
|
||||||
@ -97,7 +146,6 @@ impl Evaluator {
|
|||||||
|
|
||||||
// If there is a decompressor to use, we'll create a file in-memory and decompress it
|
// 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)?;
|
let decompression_result = decompressor.decompress(file, output_file)?;
|
||||||
if let DecompressionResult::FileInMemory(_) = decompression_result {
|
if let DecompressionResult::FileInMemory(_) = decompression_result {
|
||||||
// Should not be reachable.
|
// Should not be reachable.
|
||||||
@ -107,6 +155,11 @@ impl Evaluator {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn compress_files(files: Vec<PathBuf>, output: File) -> error::OuchResult<()> {
|
||||||
|
let (first_decompressor, second_decompressor) = Self::get_compressor(&output)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn decompress_file(file: File, output: &Option<File>) -> error::OuchResult<()> {
|
fn decompress_file(file: File, output: &Option<File>) -> error::OuchResult<()> {
|
||||||
// let output_file = &command.output;
|
// let output_file = &command.output;
|
||||||
let (first_decompressor, second_decompressor) = Self::get_decompressor(&file)?;
|
let (first_decompressor, second_decompressor) = Self::get_decompressor(&file)?;
|
||||||
@ -120,7 +173,13 @@ impl Evaluator {
|
|||||||
DecompressionResult::FileInMemory(bytes) => {
|
DecompressionResult::FileInMemory(bytes) => {
|
||||||
// We'll now decompress a file currently in memory.
|
// We'll now decompress a file currently in memory.
|
||||||
// This will currently happen in the case of .bz, .xz and .lzma
|
// This will currently happen in the case of .bz, .xz and .lzma
|
||||||
Self::decompress_file_in_memory(bytes, file_path, first_decompressor, output, extension)?;
|
Self::decompress_file_in_memory(
|
||||||
|
bytes,
|
||||||
|
file_path,
|
||||||
|
first_decompressor,
|
||||||
|
output,
|
||||||
|
extension,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
DecompressionResult::FilesUnpacked(_files) => {
|
DecompressionResult::FilesUnpacked(_files) => {
|
||||||
// If the file's last extension was an archival method,
|
// If the file's last extension was an archival method,
|
||||||
@ -138,12 +197,12 @@ impl Evaluator {
|
|||||||
|
|
||||||
pub fn evaluate(command: Command) -> error::OuchResult<()> {
|
pub fn evaluate(command: Command) -> error::OuchResult<()> {
|
||||||
let output = command.output.clone();
|
let output = command.output.clone();
|
||||||
|
|
||||||
match command.kind {
|
match command.kind {
|
||||||
CommandKind::Compression(files_to_compress) => {
|
CommandKind::Compression(files_to_compress) => {
|
||||||
for _file in files_to_compress {
|
// Safe to unwrap since output is mandatory for compression
|
||||||
todo!();
|
let output = output.unwrap();
|
||||||
}
|
Self::compress_files(files_to_compress, output)?;
|
||||||
}
|
}
|
||||||
CommandKind::Decompression(files_to_decompress) => {
|
CommandKind::Decompression(files_to_decompress) => {
|
||||||
for file in files_to_decompress {
|
for file in files_to_decompress {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::extension::{CompressionFormat, Extension};
|
use crate::extension::Extension;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
62
src/main.rs
62
src/main.rs
@ -1,4 +1,4 @@
|
|||||||
use std::{convert::TryFrom, fs, path::{Path, PathBuf}};
|
use std::{convert::TryFrom};
|
||||||
|
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
|
|
||||||
@ -13,43 +13,39 @@ mod utils;
|
|||||||
mod compressors;
|
mod compressors;
|
||||||
mod decompressors;
|
mod decompressors;
|
||||||
|
|
||||||
use compressors::{CompressionResult, Compressor, TarCompressor};
|
fn main() -> error::OuchResult<()>{
|
||||||
use error::OuchResult;
|
let print_error = |err| {
|
||||||
use file::File;
|
println!("{}: {}", "error".red(), err);
|
||||||
|
};
|
||||||
|
let matches = cli::get_matches();
|
||||||
|
match cli::Command::try_from(matches) {
|
||||||
|
Ok(command) => {
|
||||||
|
match evaluator::Evaluator::evaluate(command) {
|
||||||
|
Ok(_) => {},
|
||||||
|
Err(err) => print_error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
print_error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn main() -> OuchResult<()>{
|
// let compressor = TarCompressor {};
|
||||||
// let print_error = |err| {
|
|
||||||
// println!("{}: {}", "error".red(), err);
|
// let file = File {
|
||||||
|
// path: PathBuf::from("target"),
|
||||||
|
// contents: None,
|
||||||
|
// extension: None,
|
||||||
// };
|
// };
|
||||||
// let matches = cli::get_matches();
|
|
||||||
// match cli::Command::try_from(matches) {
|
|
||||||
// Ok(command) => {
|
|
||||||
// match evaluator::Evaluator::evaluate(command) {
|
|
||||||
// Ok(_) => {},
|
|
||||||
// Err(err) => print_error(err)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// Err(err) => {
|
|
||||||
// print_error(err)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
let compressor = TarCompressor {};
|
// let ok = compressor.compress(vec![file])?;
|
||||||
|
|
||||||
let file = File {
|
// let ok = match ok {
|
||||||
path: PathBuf::from("target"),
|
// CompressionResult::TarArchive(bytes) => bytes,
|
||||||
contents: None,
|
// _ => unreachable!()
|
||||||
extension: None,
|
// };
|
||||||
};
|
|
||||||
|
|
||||||
let ok = compressor.compress(vec![file])?;
|
// fs::write(Path::new("great.tar"), ok)?;
|
||||||
|
|
||||||
let ok = match ok {
|
|
||||||
CompressionResult::TarArchive(bytes) => bytes,
|
|
||||||
_ => unreachable!()
|
|
||||||
};
|
|
||||||
|
|
||||||
fs::write(Path::new("great.tar"), ok)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user