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