use std::{
io::{self, BufWriter, Cursor, Seek, Write},
num::NonZeroU64,
path::{Path, PathBuf},
};
use fs_err as fs;
use gzp::par::compress::{ParCompress, ParCompressBuilder};
use super::warn_user_about_loading_sevenz_in_memory;
use crate::{
BUFFER_CAPACITY, QuestionAction, QuestionPolicy, Result, archive,
commands::warn_user_about_loading_zip_in_memory,
extension::{CompressionFormat::*, Extension, split_first_compression_format},
info_accessible,
utils::{
BytesFmt, FileVisibilityPolicy, file_size,
io::lock_and_flush_output_stdio,
threads::{logical_thread_count, physical_thread_count},
user_wants_to_continue,
},
};
pub fn compress_files(
files: Vec<PathBuf>,
extensions: Vec<Extension>,
output_file: fs::File,
output_path: &Path,
follow_symlinks: bool,
question_policy: QuestionPolicy,
file_visibility_policy: FileVisibilityPolicy,
level: Option<i16>,
) -> Result<bool> {
let file_writer = BufWriter::with_capacity(BUFFER_CAPACITY, output_file);
let mut writer: Box<dyn Send + Write> = Box::new(file_writer);
let chain_writer_encoder = |format: &_, encoder| -> Result<_> {
let encoder: Box<dyn Send + Write> = match format {
Gzip => Box::new({
let parz: ParCompress<gzp::deflate::Gzip, _> = ParCompressBuilder::new()
.compression_level(
level.map_or_else(Default::default, |l| gzp::Compression::new((l as u32).clamp(0, 9))),
)
.num_threads(logical_thread_count())
.expect("gpz: num_threads must be greater than 0")
.from_writer(encoder);
parz
}),
Bzip => Box::new(bzip2::write::BzEncoder::new(
encoder,
level.map_or_else(Default::default, |l| bzip2::Compression::new((l as u32).clamp(1, 9))),
)),
Bzip3 => {
#[cfg(not(feature = "bzip3"))]
return Err(crate::Error::bzip3_no_support());
#[cfg(feature = "bzip3")]
Box::new(
bzip3::write::Bz3Encoder::new(encoder, 16 * 2_usize.pow(20))?,
)
}
Lz4 => Box::new(lz4_flex::frame::FrameEncoder::new(encoder).auto_finish()),
Lzma => {
let options = level.map_or_else(Default::default, |l| {
lzma_rust2::LzmaOptions::with_preset((l as u32).clamp(0, 9))
});
let writer = lzma_rust2::LzmaWriter::new_use_header(encoder, &options, None)?;
Box::new(writer.auto_finish())
}
Xz => {
let mut options = level.map_or_else(Default::default, |l| {
lzma_rust2::XzOptions::with_preset((l as u32).clamp(0, 9))
});
let dict_size = options.lzma_options.dict_size as u64;
options.set_block_size(NonZeroU64::new(dict_size));
let writer = lzma_rust2::XzWriterMt::new(encoder, options, physical_thread_count() as u32)?;
Box::new(writer.auto_finish())
}
Lzip => {
let options = level.map_or_else(Default::default, |l| {
lzma_rust2::LzipOptions::with_preset((l as u32).clamp(0, 9))
});
let writer = lzma_rust2::LzipWriter::new(encoder, options);
Box::new(writer.auto_finish())
}
Snappy => Box::new({
let parz: ParCompress<gzp::snap::Snap, _> = ParCompressBuilder::new()
.compression_level(gzp::par::compress::Compression::new(
level.map_or_else(Default::default, |l| (l as u32).clamp(0, 9)),
))
.num_threads(logical_thread_count())
.expect("gpz: num_threads must be greater than 0")
.from_writer(encoder);
parz
}),
Zstd => {
let mut zstd_encoder = zstd::stream::write::Encoder::new(
encoder,
level.map_or(zstd::DEFAULT_COMPRESSION_LEVEL, |l| {
(l as i32).clamp(zstd::zstd_safe::min_c_level(), zstd::zstd_safe::max_c_level())
}),
)?;
zstd_encoder.multithread(physical_thread_count() as u32)?;
Box::new(zstd_encoder.auto_finish())
}
Brotli => {
let default_level = 11; let level = level.unwrap_or(default_level).clamp(0, 11) as u32;
let win_size = 22; Box::new(brotli::CompressorWriter::new(encoder, BUFFER_CAPACITY, level, win_size))
}
Tar | Zip | Rar | SevenZip => unreachable!(),
};
Ok(encoder)
};
let (first_format, formats) = split_first_compression_format(&extensions);
for format in formats.iter().rev() {
writer = chain_writer_encoder(format, writer)?;
}
match first_format {
Gzip | Bzip | Bzip3 | Lz4 | Lzma | Xz | Lzip | Snappy | Zstd | Brotli => {
writer = chain_writer_encoder(&first_format, writer)?;
let mut reader = fs::File::open(&files[0])?;
io::copy(&mut reader, &mut writer)?;
info_accessible!("Input file size: {}", BytesFmt(file_size(&files[0])?));
}
Tar => {
archive::tar::build_archive(
&files,
output_path,
&mut writer,
file_visibility_policy,
follow_symlinks,
)?;
writer.flush()?;
}
Zip => {
if !formats.is_empty() {
let _locks = lock_and_flush_output_stdio();
warn_user_about_loading_zip_in_memory();
if !user_wants_to_continue(output_path, question_policy, QuestionAction::Compression)? {
return Ok(false);
}
}
let mut vec_buffer = Cursor::new(vec![]);
archive::zip::build_archive(
&files,
output_path,
&mut vec_buffer,
file_visibility_policy,
follow_symlinks,
)?;
vec_buffer.rewind()?;
io::copy(&mut vec_buffer, &mut writer)?;
}
Rar => {
#[cfg(feature = "unrar")]
return Err(archive::rar::no_compression());
#[cfg(not(feature = "unrar"))]
return Err(crate::Error::rar_no_support());
}
SevenZip => {
if !formats.is_empty() {
let _locks = lock_and_flush_output_stdio();
warn_user_about_loading_sevenz_in_memory();
if !user_wants_to_continue(output_path, question_policy, QuestionAction::Compression)? {
return Ok(false);
}
}
let mut vec_buffer = Cursor::new(vec![]);
archive::sevenz::build_archive(&files, output_path, &mut vec_buffer, file_visibility_policy)?;
vec_buffer.rewind()?;
io::copy(&mut vec_buffer, &mut writer)?;
}
}
Ok(true)
}