decompressors/tar: Add confirmation dialog for file overwriting
[ouch.git] / src / evaluator.rs
blob8e923b143b7946c3df0fdf1a00152c1101baf4b7
1 use std::{ffi::OsStr, fs, io::Write, path::PathBuf};
3 use colored::Colorize;
5 use crate::{
6     cli::{Command, CommandKind},
7     compressors::{
8         BzipCompressor, Compressor, Entry, GzipCompressor, LzmaCompressor, TarCompressor,
9         ZipCompressor,
10     },
11     decompressors::{
12         BzipDecompressor, DecompressionResult, Decompressor, GzipDecompressor, LzmaDecompressor,
13         TarDecompressor, ZipDecompressor,
14     },
15     extension::{CompressionFormat, Extension},
16     file::File,
17     utils,
20 pub struct Evaluator {}
22 impl Evaluator {
23     pub fn get_compressor(
24         file: &File,
25     ) -> crate::Result<(Option<Box<dyn Compressor>>, Box<dyn Compressor>)> {
26         let extension = match &file.extension {
27             Some(extension) => extension.clone(),
28             None => {
29                 // This block *should* be unreachable
30                 eprintln!(
31                     "{}: reached Evaluator::get_decompressor without known extension.",
32                     "internal error".red()
33                 );
34                 return Err(crate::Error::InvalidInput);
35             }
36         };
38         // Supported first compressors:
39         // .tar and .zip
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 {})),
45                 _other => {
46                     todo!();
47                 }
48             },
49             None => None,
50         };
52         // Supported second compressors:
53         // any
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 {}),
60         };
62         Ok((first_compressor, second_compressor))
63     }
65     pub fn get_decompressor(
66         file: &File,
67     ) -> crate::Result<(Option<Box<dyn Decompressor>>, Box<dyn Decompressor>)> {
68         let extension = match &file.extension {
69             Some(extension) => extension.clone(),
70             None => {
71                 // This block *should* be unreachable
72                 eprintln!(
73                     "{}: reached Evaluator::get_decompressor without known extension.",
74                     "internal error".red()
75                 );
76                 return Err(crate::Error::InvalidInput);
77             }
78         };
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 {}),
86         };
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 {})),
92                 _other => None,
93             },
94             None => None,
95         };
97         Ok((first_decompressor, second_decompressor))
98     }
100     // todo: move this folder into decompressors/ later on
101     fn decompress_file_in_memory(
102         bytes: Vec<u8>,
103         file_path: PathBuf,
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
111             .file_stem()
112             .unwrap_or_else(|| output_file_path.as_os_str());
114         if filename == "." {
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");
118         }
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,
124             None => {
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)?;
131                 return Ok(());
132             }
133         };
135         let file = File {
136             path: filename,
137             contents_in_memory: Some(bytes),
138             extension,
139         };
141         let decompression_result = decompressor.decompress(file, output_file)?;
142         if let DecompressionResult::FileInMemory(_) = decompression_result {
143             // Should not be reachable.
144             unreachable!();
145         }
147         Ok(())
148     }
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)?
163             }
164             None => {
165                 let entry = Entry::Files(files);
166                 second_compressor.compress(entry)?
167             }
168         };
170         println!(
171             "{}: writing to {:?}. ({} bytes)",
172             "info".yellow(),
173             &output_path,
174             bytes.len()
175         );
176         fs::write(output_path, bytes)?;
178         Ok(())
179     }
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(
195                     bytes,
196                     file_path,
197                     first_decompressor,
198                     output,
199                     extension,
200                 )?;
201             }
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
210             }
211         }
213         Ok(())
214     }
216     pub fn evaluate(command: Command) -> crate::Result<()> {
217         let output = command.output.clone();
219         match command.kind {
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)?;
224             }
225             CommandKind::Decompression(files_to_decompress) => {
226                 for file in files_to_decompress {
227                     Self::decompress_file(file, &output)?;
228                 }
229             }
230         }
231         Ok(())
232     }