(WIP) Minor misc. changes
[ouch.git] / src / evaluator.rs
bloba4986b6812e3364cb05c14b06bba0ee4e8e0bfe0
1 use std::{
2     fs,
3     io::Write,
4     path::{Path, PathBuf},
5 };
7 use colored::Colorize;
9 use crate::{
10     cli::{VERSION, Command},
11     compressors::{
12         Entry, Compressor, BzipCompressor, GzipCompressor, LzmaCompressor, TarCompressor,
13         ZipCompressor,
14     },
15     decompressors::{
16         BzipDecompressor, DecompressionResult, Decompressor, GzipDecompressor, LzmaDecompressor,
17         TarDecompressor, ZipDecompressor,
18     }, 
19     dialogs::Confirmation, 
20     extension::{CompressionFormat, Extension}, 
21     file::File, 
22     utils
25 pub struct Evaluator {}
27 type BoxedCompressor = Box<dyn Compressor>;
28 type BoxedDecompressor = Box<dyn Decompressor>;
30 impl Evaluator {
31     pub fn get_compressor(
32         file: &File,
33     ) -> crate::Result<(Option<BoxedCompressor>, BoxedCompressor)> {
34         
35         let extension = match &file.extension {
36             Some(extension) => extension.clone(),
37             None => {
38                 // This block *should* be unreachable
39                 eprintln!(
40                     "{} reached Evaluator::get_decompressor without known extension.",
41                     "[internal error]".red()
42                 );
43                 return Err(crate::Error::InternalError);
44             }
45         };
47         // Supported first compressors:
48         // .tar and .zip
49         let first_compressor: Option<Box<dyn Compressor>> = match extension.first_ext {
50             Some(ext) => match ext {
51                 CompressionFormat::Tar => Some(Box::new(TarCompressor {})),
52                 CompressionFormat::Zip => Some(Box::new(ZipCompressor {})),
53                 // _other => Some(Box::new(NifflerCompressor {})),
54                 _other => {
55                     todo!();
56                 }
57             },
58             None => None,
59         };
61         // Supported second compressors:
62         // any
63         let second_compressor: Box<dyn Compressor> = match extension.second_ext {
64             CompressionFormat::Tar => Box::new(TarCompressor {}),
65             CompressionFormat::Zip => Box::new(ZipCompressor {}),
66             CompressionFormat::Bzip => Box::new(BzipCompressor {}),
67             CompressionFormat::Gzip => Box::new(GzipCompressor {}),
68             CompressionFormat::Lzma => Box::new(LzmaCompressor {}),
69         };
71         Ok((first_compressor, second_compressor))
72     }
74     pub fn get_decompressor(
75         file: &File,
76     ) -> crate::Result<(Option<BoxedDecompressor>, BoxedDecompressor)> {
77         let extension = match &file.extension {
78             Some(extension) => extension.clone(),
79             None => {
80                 // This block *should* be unreachable
81                 eprintln!(
82                     "{} reached Evaluator::get_decompressor without known extension.",
83                     "[internal error]".red()
84                 );
85                 return Err(crate::Error::InvalidInput);
86             }
87         };
89         let second_decompressor: Box<dyn Decompressor> = match extension.second_ext {
90             CompressionFormat::Tar => Box::new(TarDecompressor {}),
91             CompressionFormat::Zip => Box::new(ZipDecompressor {}),
92             CompressionFormat::Gzip => Box::new(GzipDecompressor {}),
93             CompressionFormat::Lzma => Box::new(LzmaDecompressor {}),
94             CompressionFormat::Bzip => Box::new(BzipDecompressor {}),
95         };
97         let first_decompressor: Option<Box<dyn Decompressor>> = match extension.first_ext {
98             Some(ext) => match ext {
99                 CompressionFormat::Tar => Some(Box::new(TarDecompressor {})),
100                 CompressionFormat::Zip => Some(Box::new(ZipDecompressor {})),
101                 _other => None,
102             },
103             None => None,
104         };
106         Ok((first_decompressor, second_decompressor))
107     }
109     fn decompress_file_in_memory(
110         bytes: Vec<u8>,
111         file_path: &Path,
112         decompressor: Option<Box<dyn Decompressor>>,
113         output_file: Option<File>,
114         extension: Option<Extension>,
115         flags: &oof::Flags,
116     ) -> crate::Result<()> {
117         let output_file_path = utils::get_destination_path(&output_file);
119         let file_name = file_path
120             .file_stem()
121             .map(Path::new)
122             .unwrap_or(output_file_path);
124         if "." == file_name.as_os_str() {
125             // I believe this is only possible when the supplied input has a name
126             // of the sort `.tar` or `.zip' and no output has been supplied.
127             // file_name = OsStr::new("ouch-output");
128             todo!("Pending review, what is this supposed to do??");
129         }
131         // If there is a decompressor to use, we'll create a file in-memory and decompress it
132         let decompressor = match decompressor {
133             Some(decompressor) => decompressor,
134             None => {
135                 // There is no more processing to be done on the input file (or there is but currently unsupported)
136                 // Therefore, we'll save what we have in memory into a file.
137                 println!("{}: saving to {:?}.", "info".yellow(), file_name);
139                 if file_name.exists() {
140                     let confirm =
141                         Confirmation::new("Do you want to overwrite 'FILE'?", Some("FILE"));
142                     if !utils::permission_for_overwriting(&file_name, flags, &confirm)? {
143                         return Ok(());
144                     }
145                 }
147                 let mut f = fs::File::create(output_file_path.join(file_name))?;
148                 f.write_all(&bytes)?;
149                 return Ok(());
150             }
151         };
153         let file = File {
154             path: file_name,
155             contents_in_memory: Some(bytes),
156             extension,
157         };
159         let decompression_result = decompressor.decompress(file, &output_file, flags)?;
160         if let DecompressionResult::FileInMemory(_) = decompression_result {
161             unreachable!("Shouldn't");
162         }
164         Ok(())
165     }
167     fn compress_files(
168         files: Vec<PathBuf>,
169         output_path: &Path,
170         flags: &oof::Flags,
171     ) -> crate::Result<()> {
172         let mut output = File::from(output_path)?;
174         let confirm = Confirmation::new("Do you want to overwrite 'FILE'?", Some("FILE"));
175         let (first_compressor, second_compressor) = Self::get_compressor(&output)?;
177         // TODO: use -y and -n here
178         if output_path.exists()
179             && !utils::permission_for_overwriting(&output_path, flags, &confirm)?
180         {
181             // The user does not want to overwrite the file
182             return Ok(());
183         }
185         let bytes = match first_compressor {
186             Some(first_compressor) => {
187                 let mut entry = Entry::Files(files);
188                 let bytes = first_compressor.compress(entry)?;
190                 output.contents_in_memory = Some(bytes);
191                 entry = Entry::InMemory(output);
192                 second_compressor.compress(entry)?
193             }
194             None => {
195                 let entry = Entry::Files(files);
196                 second_compressor.compress(entry)?
197             }
198         };
200         println!(
201             "{}: writing to {:?}. ({} bytes)",
202             "info".yellow(),
203             output_path,
204             bytes.len()
205         );
206         fs::write(output_path, bytes)?;
208         Ok(())
209     }
211     fn decompress_file(
212         file_path: &Path,
213         output: Option<&Path>,
214         flags: &oof::Flags,
215     ) -> crate::Result<()> {        
216         let file = File::from(file_path)?;
217         let output = match output {
218             Some(inner) => Some(File::from(inner)?),
219             None => None,
220         };
221         let (first_decompressor, second_decompressor) = Self::get_decompressor(&file)?;
223         let extension = file.extension.clone();
225         let decompression_result = second_decompressor.decompress(file, &output, &flags)?;
227         match decompression_result {
228             DecompressionResult::FileInMemory(bytes) => {
229                 // We'll now decompress a file currently in memory.
230                 // This will currently happen in the case of .bz, .xz and .lzma
231                 Self::decompress_file_in_memory(
232                     bytes,
233                     file_path,
234                     first_decompressor,
235                     output,
236                     extension,
237                     flags,
238                 )?;
239             }
240             DecompressionResult::FilesUnpacked(_files) => {
241                 // If the file's last extension was an archival method,
242                 // such as .tar, .zip or (to-do) .rar, then we won't look for
243                 // further processing.
244                 // The reason for this is that cases such as "file.xz.tar" are too rare
245                 // to worry about, at least at the moment.
247                 // TODO: use the `files` variable for something
248             }
249         }
251         Ok(())
252     }
254     pub fn evaluate(command: Command, flags: &oof::Flags) -> crate::Result<()> {
255         match command {
256             Command::Compress {
257                 files,
258                 compressed_output_path,
259             } => Self::compress_files(files, &compressed_output_path, flags)?,
260             Command::Decompress {
261                 files,
262                 output_folder,
263             } => {
264                 // From Option<PathBuf> to Option<&Path>
265                 let output_folder = output_folder.as_ref().map(|path| Path::new(path));
266                 for file in files.iter() {
267                     Self::decompress_file(file, output_folder, flags)?;
268                 }
269             }
270             Command::ShowHelp => help_message(),
271             Command::ShowVersion => version_message(),
272         }
273         Ok(())
274     }
277 #[inline]
278 fn version_message() {
279     println!("ouch {}", VERSION);
282 fn help_message() {
283     version_message();
284     println!("Vinícius R. M. & João M. Bezerra");
285     println!("ouch is a unified compression & decompression utility");
286     println!();
287     println!(" COMPRESSION USAGE:");
288     println!("    ouch compress <input...> output-file");
289     println!("DECOMPRESSION USAGE:");
290     println!("    ouch <input> [-o/--output output-folder]");