fix: now recursivly compress 7z
[ouch.git] / src / commands / compress.rs
blob2ee20c52acd8cbf1f5f4b93685901a48e0a27dee
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 use super::copy_recursively;
18 /// Compress files into `output_file`.
19 ///
20 /// # Arguments:
21 /// - `files`: is the list of paths to be compressed: ["dir/file1.txt", "dir/file2.txt"]
22 /// - `extensions`: is a list of compression formats for compressing, example: [Tar, Gz] (in compression order)
23 /// - `output_file` is the resulting compressed file name, example: "archive.tar.gz"
24 ///
25 /// # Return value
26 /// - Returns `Ok(true)` if compressed all files normally.
27 /// - Returns `Ok(false)` if user opted to abort compression mid-way.
28 #[allow(clippy::too_many_arguments)]
29 pub fn compress_files(
30     files: Vec<PathBuf>,
31     extensions: Vec<Extension>,
32     output_file: fs::File,
33     output_path: &Path,
34     quiet: bool,
35     question_policy: QuestionPolicy,
36     file_visibility_policy: FileVisibilityPolicy,
37     level: Option<i16>,
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 Send + Write> = Box::new(file_writer);
44     // Grab previous encoder and wrap it inside of a new one
45     let chain_writer_encoder = |format: &_, encoder| -> crate::Result<_> {
46         let encoder: Box<dyn Send + Write> = match format {
47             Gzip => Box::new(
48                 // by default, ParCompress uses a default compression level of 3
49                 // instead of the regular default that flate2 uses
50                 gzp::par::compress::ParCompress::<gzp::deflate::Gzip>::builder()
51                     .compression_level(
52                         level.map_or_else(Default::default, |l| gzp::Compression::new((l as u32).clamp(0, 9))),
53                     )
54                     .from_writer(encoder),
55             ),
56             Bzip => Box::new(bzip2::write::BzEncoder::new(
57                 encoder,
58                 level.map_or_else(Default::default, |l| bzip2::Compression::new((l as u32).clamp(1, 9))),
59             )),
60             Lz4 => Box::new(lz4_flex::frame::FrameEncoder::new(encoder).auto_finish()),
61             Lzma => Box::new(xz2::write::XzEncoder::new(
62                 encoder,
63                 level.map_or(6, |l| (l as u32).clamp(0, 9)),
64             )),
65             Snappy => Box::new(
66                 gzp::par::compress::ParCompress::<gzp::snap::Snap>::builder()
67                     .compression_level(gzp::par::compress::Compression::new(
68                         level.map_or_else(Default::default, |l| (l as u32).clamp(0, 9)),
69                     ))
70                     .from_writer(encoder),
71             ),
72             Zstd => {
73                 let zstd_encoder = zstd::stream::write::Encoder::new(
74                     encoder,
75                     level.map_or(zstd::DEFAULT_COMPRESSION_LEVEL, |l| {
76                         (l as i32).clamp(zstd::zstd_safe::min_c_level(), zstd::zstd_safe::max_c_level())
77                     }),
78                 );
79                 // Safety:
80                 //     Encoder::new() can only fail if `level` is invalid, but the level
81                 //     is `clamp`ed and therefore guaranteed to be valid
82                 Box::new(zstd_encoder.unwrap().auto_finish())
83             }
84             Tar | Zip | Rar | SevenZip => unreachable!(),
85         };
86         Ok(encoder)
87     };
89     let (first_format, formats) = split_first_compression_format(&extensions);
91     for format in formats.iter().rev() {
92         writer = chain_writer_encoder(format, writer)?;
93     }
95     match first_format {
96         Gzip | Bzip | Lz4 | Lzma | Snappy | Zstd => {
97             writer = chain_writer_encoder(&first_format, writer)?;
98             let mut reader = fs::File::open(&files[0]).unwrap();
100             io::copy(&mut reader, &mut writer)?;
101         }
102         Tar => {
103             archive::tar::build_archive_from_paths(&files, output_path, &mut writer, file_visibility_policy, quiet)?;
104             writer.flush()?;
105         }
106         Zip => {
107             if !formats.is_empty() {
108                 warn_user_about_loading_zip_in_memory();
110                 if !user_wants_to_continue(output_path, question_policy, QuestionAction::Compression)? {
111                     return Ok(false);
112                 }
113             }
115             let mut vec_buffer = Cursor::new(vec![]);
117             archive::zip::build_archive_from_paths(
118                 &files,
119                 output_path,
120                 &mut vec_buffer,
121                 file_visibility_policy,
122                 quiet,
123             )?;
124             vec_buffer.rewind()?;
125             io::copy(&mut vec_buffer, &mut writer)?;
126         },
127         Rar => {
128             archive::rar::no_compression_notice();
129             return Ok(false);
130         },
131         SevenZip => {
132             let tmpdir = tempfile::tempdir()?;
134             for filep in files.iter() {
135                 if filep.is_dir() {
136                     copy_recursively(filep, tmpdir.path()
137                         .join(filep.strip_prefix(std::env::current_dir()?).expect("copy folder error")))?;
138                 } else {
139                     fs::copy(filep, tmpdir.path().join(filep.file_name().expect("no filename in file")))?;
140                 }
141             }
143             sevenz_rust::compress_to_path(tmpdir.path(), output_path).expect("can't compress 7zip archive");
144         }
145     }
147     Ok(true)