2 io::{self, BufWriter, Cursor, Seek, Write},
8 use super::warn_user_about_loading_sevenz_in_memory;
11 commands::warn_user_about_loading_zip_in_memory,
12 extension::{split_first_compression_format, CompressionFormat::*, Extension},
13 utils::{user_wants_to_continue, FileVisibilityPolicy},
14 QuestionAction, QuestionPolicy, BUFFER_CAPACITY,
17 /// Compress files into `output_file`.
20 /// - `files`: is the list of paths to be compressed: ["dir/file1.txt", "dir/file2.txt"]
21 /// - `extensions`: is a list of compression formats for compressing, example: [Tar, Gz] (in compression order)
22 /// - `output_file` is the resulting compressed file name, example: "archive.tar.gz"
25 /// - Returns `Ok(true)` if compressed all files normally.
26 /// - Returns `Ok(false)` if user opted to abort compression mid-way.
27 #[allow(clippy::too_many_arguments)]
28 pub fn compress_files(
30 extensions: Vec<Extension>,
31 output_file: fs::File,
34 question_policy: QuestionPolicy,
35 file_visibility_policy: FileVisibilityPolicy,
37 ) -> crate::Result<bool> {
38 // If the input files contain a directory, then the total size will be underestimated
39 let file_writer = BufWriter::with_capacity(BUFFER_CAPACITY, output_file);
41 let mut writer: Box<dyn Send + Write> = Box::new(file_writer);
43 // Grab previous encoder and wrap it inside of a new one
44 let chain_writer_encoder = |format: &_, encoder| -> crate::Result<_> {
45 let encoder: Box<dyn Send + Write> = match format {
47 // by default, ParCompress uses a default compression level of 3
48 // instead of the regular default that flate2 uses
49 gzp::par::compress::ParCompress::<gzp::deflate::Gzip>::builder()
51 level.map_or_else(Default::default, |l| gzp::Compression::new((l as u32).clamp(0, 9))),
53 .from_writer(encoder),
55 Bzip => Box::new(bzip2::write::BzEncoder::new(
57 level.map_or_else(Default::default, |l| bzip2::Compression::new((l as u32).clamp(1, 9))),
59 Lz4 => Box::new(lz4_flex::frame::FrameEncoder::new(encoder).auto_finish()),
60 Lzma => Box::new(xz2::write::XzEncoder::new(
62 level.map_or(6, |l| (l as u32).clamp(0, 9)),
65 gzp::par::compress::ParCompress::<gzp::snap::Snap>::builder()
66 .compression_level(gzp::par::compress::Compression::new(
67 level.map_or_else(Default::default, |l| (l as u32).clamp(0, 9)),
69 .from_writer(encoder),
72 let zstd_encoder = zstd::stream::write::Encoder::new(
74 level.map_or(zstd::DEFAULT_COMPRESSION_LEVEL, |l| {
75 (l as i32).clamp(zstd::zstd_safe::min_c_level(), zstd::zstd_safe::max_c_level())
79 // Encoder::new() can only fail if `level` is invalid, but the level
80 // is `clamp`ed and therefore guaranteed to be valid
81 Box::new(zstd_encoder.unwrap().auto_finish())
83 Tar | Zip | Rar | SevenZip => unreachable!(),
88 let (first_format, formats) = split_first_compression_format(&extensions);
90 for format in formats.iter().rev() {
91 writer = chain_writer_encoder(format, writer)?;
95 Gzip | Bzip | Lz4 | Lzma | Snappy | Zstd => {
96 writer = chain_writer_encoder(&first_format, writer)?;
97 let mut reader = fs::File::open(&files[0]).unwrap();
99 io::copy(&mut reader, &mut writer)?;
102 archive::tar::build_archive_from_paths(&files, output_path, &mut writer, file_visibility_policy, quiet)?;
106 if !formats.is_empty() {
107 warn_user_about_loading_zip_in_memory();
109 if !user_wants_to_continue(output_path, question_policy, QuestionAction::Compression)? {
114 let mut vec_buffer = Cursor::new(vec![]);
116 archive::zip::build_archive_from_paths(
120 file_visibility_policy,
123 vec_buffer.rewind()?;
124 io::copy(&mut vec_buffer, &mut writer)?;
127 archive::rar::no_compression_notice();
131 if !formats.is_empty() {
132 warn_user_about_loading_sevenz_in_memory();
134 if !user_wants_to_continue(output_path, question_policy, QuestionAction::Compression)? {
139 let mut vec_buffer = Cursor::new(vec![]);
140 archive::sevenz::compress_sevenz(&files, output_path, &mut vec_buffer, file_visibility_policy, quiet)?;
141 vec_buffer.rewind()?;
142 io::copy(&mut vec_buffer, &mut writer)?;