1 use std::{ffi::OsStr, fs, io::Write, path::PathBuf};
6 cli::{Command, CommandKind},
8 BzipCompressor, Compressor, Entry, GzipCompressor, LzmaCompressor, TarCompressor,
12 BzipDecompressor, DecompressionResult, Decompressor, GzipDecompressor, LzmaDecompressor,
13 TarDecompressor, ZipDecompressor,
15 extension::{CompressionFormat, Extension},
20 pub struct Evaluator {}
23 pub fn get_compressor(
25 ) -> crate::Result<(Option<Box<dyn Compressor>>, Box<dyn Compressor>)> {
26 let extension = match &file.extension {
27 Some(extension) => extension.clone(),
29 // This block *should* be unreachable
31 "{}: reached Evaluator::get_decompressor without known extension.",
32 "internal error".red()
34 return Err(crate::Error::InvalidInput);
38 // Supported first compressors:
40 let first_compressor: Option<Box<dyn Compressor>> = match extension.first_ext {
41 Some(ext) => match ext {
42 CompressionFormat::Tar => Some(Box::new(TarCompressor {})),
43 CompressionFormat::Zip => Some(Box::new(ZipCompressor {})),
44 // _other => Some(Box::new(NifflerCompressor {})),
52 // Supported second compressors:
54 let second_compressor: Box<dyn Compressor> = match extension.second_ext {
55 CompressionFormat::Tar => Box::new(TarCompressor {}),
56 CompressionFormat::Zip => Box::new(ZipCompressor {}),
57 CompressionFormat::Bzip => Box::new(BzipCompressor {}),
58 CompressionFormat::Gzip => Box::new(GzipCompressor {}),
59 CompressionFormat::Lzma => Box::new(LzmaCompressor {}),
62 Ok((first_compressor, second_compressor))
65 pub fn get_decompressor(
67 ) -> crate::Result<(Option<Box<dyn Decompressor>>, Box<dyn Decompressor>)> {
68 let extension = match &file.extension {
69 Some(extension) => extension.clone(),
71 // This block *should* be unreachable
73 "{}: reached Evaluator::get_decompressor without known extension.",
74 "internal error".red()
76 return Err(crate::Error::InvalidInput);
80 let second_decompressor: Box<dyn Decompressor> = match extension.second_ext {
81 CompressionFormat::Tar => Box::new(TarDecompressor {}),
82 CompressionFormat::Zip => Box::new(ZipDecompressor {}),
83 CompressionFormat::Gzip => Box::new(GzipDecompressor {}),
84 CompressionFormat::Lzma => Box::new(LzmaDecompressor {}),
85 CompressionFormat::Bzip => Box::new(BzipDecompressor {}),
88 let first_decompressor: Option<Box<dyn Decompressor>> = match extension.first_ext {
89 Some(ext) => match ext {
90 CompressionFormat::Tar => Some(Box::new(TarDecompressor {})),
91 CompressionFormat::Zip => Some(Box::new(ZipDecompressor {})),
97 Ok((first_decompressor, second_decompressor))
100 // todo: move this folder into decompressors/ later on
101 fn decompress_file_in_memory(
104 decompressor: Option<Box<dyn Decompressor>>,
105 output_file: &Option<File>,
106 extension: Option<Extension>,
107 ) -> crate::Result<()> {
108 let output_file_path = utils::get_destination_path(output_file);
110 let mut filename = file_path
112 .unwrap_or_else(|| output_file_path.as_os_str());
115 // I believe this is only possible when the supplied inout has a name
116 // of the sort `.tar` or `.zip' and no output has been supplied.
117 filename = OsStr::new("ouch-output");
119 let filename = PathBuf::from(filename);
121 // If there is a decompressor to use, we'll create a file in-memory and decompress it
122 let decompressor = match decompressor {
123 Some(decompressor) => decompressor,
125 // There is no more processing to be done on the input file (or there is but currently unsupported)
126 // Therefore, we'll save what we have in memory into a file.
127 println!("{}: saving to {:?}.", "info".yellow(), filename);
129 let mut f = fs::File::create(output_file_path.join(filename))?;
130 f.write_all(&bytes)?;
137 contents_in_memory: Some(bytes),
141 let decompression_result = decompressor.decompress(file, output_file)?;
142 if let DecompressionResult::FileInMemory(_) = decompression_result {
143 // Should not be reachable.
150 fn compress_files(files: Vec<PathBuf>, mut output: File) -> crate::Result<()> {
151 let (first_compressor, second_compressor) = Self::get_compressor(&output)?;
153 let output_path = output.path.clone();
155 let bytes = match first_compressor {
156 Some(first_compressor) => {
157 let mut entry = Entry::Files(files);
158 let bytes = first_compressor.compress(entry)?;
160 output.contents_in_memory = Some(bytes);
161 entry = Entry::InMemory(output);
162 second_compressor.compress(entry)?
165 let entry = Entry::Files(files);
166 second_compressor.compress(entry)?
171 "{}: writing to {:?}. ({} bytes)",
176 fs::write(output_path, bytes)?;
181 fn decompress_file(file: File, output: &Option<File>) -> crate::Result<()> {
182 // let output_file = &command.output;
183 let (first_decompressor, second_decompressor) = Self::get_decompressor(&file)?;
185 let file_path = file.path.clone();
186 let extension = file.extension.clone();
188 let decompression_result = second_decompressor.decompress(file, output)?;
190 match decompression_result {
191 DecompressionResult::FileInMemory(bytes) => {
192 // We'll now decompress a file currently in memory.
193 // This will currently happen in the case of .bz, .xz and .lzma
194 Self::decompress_file_in_memory(
202 DecompressionResult::FilesUnpacked(_files) => {
203 // If the file's last extension was an archival method,
204 // such as .tar, .zip or (to-do) .rar, then we won't look for
205 // further processing.
206 // The reason for this is that cases such as "file.xz.tar" are too rare
207 // to worry about, at least at the moment.
209 // TODO: use the `files` variable for something
216 pub fn evaluate(command: Command) -> crate::Result<()> {
217 let output = command.output.clone();
220 CommandKind::Compression(files_to_compress) => {
221 // Safe to unwrap since output is mandatory for compression
222 let output = output.unwrap();
223 Self::compress_files(files_to_compress, output)?;
225 CommandKind::Decompression(files_to_decompress) => {
226 for file in files_to_decompress {
227 Self::decompress_file(file, &output)?;