Decompress files in parallel
[ouch.git] / src / commands / compress.rs
blob12f6eef98f9186f6892f37b14e0cc764bf01ef20
1 use std::{
2     io::{self, BufWriter, Cursor, Seek, Write},
3     path::{Path, PathBuf},
4 };
6 use fs_err as fs;
8 use crate::{
9     archive,
10     commands::warn_user_about_loading_zip_in_memory,
11     extension::{
12         split_first_compression_format,
13         CompressionFormat::{self, *},
14         Extension,
15     },
16     utils::{user_wants_to_continue, FileVisibilityPolicy},
17     QuestionAction, QuestionPolicy, BUFFER_CAPACITY,
20 /// Compress files into `output_file`.
21 ///
22 /// # Arguments:
23 /// - `files`: is the list of paths to be compressed: ["dir/file1.txt", "dir/file2.txt"]
24 /// - `extensions`: is a list of compression formats for compressing, example: [Tar, Gz] (in compression order)
25 /// - `output_file` is the resulting compressed file name, example: "archive.tar.gz"
26 ///
27 /// # Return value
28 /// - Returns `Ok(true)` if compressed all files normally.
29 /// - Returns `Ok(false)` if user opted to abort compression mid-way.
30 pub fn compress_files(
31     files: Vec<PathBuf>,
32     extensions: Vec<Extension>,
33     output_file: fs::File,
34     output_path: &Path,
35     quiet: bool,
36     question_policy: QuestionPolicy,
37     file_visibility_policy: FileVisibilityPolicy,
38 ) -> crate::Result<bool> {
39     // If the input files contain a directory, then the total size will be underestimated
40     let file_writer = BufWriter::with_capacity(BUFFER_CAPACITY, output_file);
42     let mut writer: Box<dyn Write> = Box::new(file_writer);
44     // Grab previous encoder and wrap it inside of a new one
45     let chain_writer_encoder = |format: &CompressionFormat, encoder: Box<dyn Write>| -> crate::Result<Box<dyn Write>> {
46         let encoder: Box<dyn Write> = match format {
47             Gzip => Box::new(flate2::write::GzEncoder::new(encoder, Default::default())),
48             Bzip => Box::new(bzip2::write::BzEncoder::new(encoder, Default::default())),
49             Lz4 => Box::new(lzzzz::lz4f::WriteCompressor::new(encoder, Default::default())?),
50             Lzma => Box::new(xz2::write::XzEncoder::new(encoder, 6)),
51             Snappy => Box::new(snap::write::FrameEncoder::new(encoder)),
52             Zstd => {
53                 let zstd_encoder = zstd::stream::write::Encoder::new(encoder, Default::default());
54                 // Safety:
55                 //     Encoder::new() can only fail if `level` is invalid, but Default::default()
56                 //     is guaranteed to be valid
57                 Box::new(zstd_encoder.unwrap().auto_finish())
58             }
59             Tar | Zip => unreachable!(),
60         };
61         Ok(encoder)
62     };
64     let (first_format, formats) = split_first_compression_format(&extensions);
66     for format in formats.iter().rev() {
67         writer = chain_writer_encoder(format, writer)?;
68     }
70     match first_format {
71         Gzip | Bzip | Lz4 | Lzma | Snappy | Zstd => {
72             writer = chain_writer_encoder(&first_format, writer)?;
73             let mut reader = fs::File::open(&files[0]).unwrap();
75             io::copy(&mut reader, &mut writer)?;
76         }
77         Tar => {
78             archive::tar::build_archive_from_paths(&files, output_path, &mut writer, file_visibility_policy, quiet)?;
79             writer.flush()?;
80         }
81         Zip => {
82             if !formats.is_empty() {
83                 warn_user_about_loading_zip_in_memory();
85                 if !user_wants_to_continue(output_path, question_policy, QuestionAction::Compression)? {
86                     return Ok(false);
87                 }
88             }
90             let mut vec_buffer = Cursor::new(vec![]);
92             archive::zip::build_archive_from_paths(
93                 &files,
94                 output_path,
95                 &mut vec_buffer,
96                 file_visibility_policy,
97                 quiet,
98             )?;
99             vec_buffer.rewind()?;
100             io::copy(&mut vec_buffer, &mut writer)?;
101         }
102     }
104     Ok(true)