use std::io::{BufReader, BufWriter};
use camino::{Utf8Path, Utf8PathBuf};
use crate::error::Result;
use crate::filter;
use crate::{ArchiveInfo, CompressOpts, DecompressOpts, Entry};
pub(crate) fn validate_level(level: u32) -> Result<u32> {
if level > 9 {
return Err(std::io::Error::other(format!(
"xz compression level must be 0..=9 (got {level})"
))
.into());
}
Ok(level)
}
#[cfg(feature = "xz2")]
pub fn compress(inputs: &[Utf8PathBuf], output: &Utf8Path, opts: &CompressOpts<'_>) -> Result<()> {
let inputs = filter::validate_inputs(inputs, opts)?;
let level = validate_level(opts.level.unwrap_or(6))?;
let file = fs_err::File::create(output)?;
let buf = BufWriter::new(file);
let encoder = xz2::write::XzEncoder::new(buf, level);
let mut builder = tar::Builder::new(encoder);
builder.follow_symlinks(opts.follow_symlinks);
filter::append_inputs(&mut builder, &inputs, opts)?;
let encoder = builder.into_inner()?;
let buf = encoder.finish()?;
let file = buf.into_inner().map_err(std::io::Error::other)?;
file.sync_all()?;
Ok(())
}
#[cfg(not(feature = "xz2"))]
pub fn compress(inputs: &[Utf8PathBuf], output: &Utf8Path, opts: &CompressOpts<'_>) -> Result<()> {
let inputs = filter::validate_inputs(inputs, opts)?;
let level = validate_level(opts.level.unwrap_or(6))?;
let file = fs_err::File::create(output)?;
let buf = BufWriter::new(file);
let encoder = lzma_rust2::XzWriter::new(buf, lzma_rust2::XzOptions::with_preset(level))?;
let mut builder = tar::Builder::new(encoder);
builder.follow_symlinks(opts.follow_symlinks);
filter::append_inputs(&mut builder, &inputs, opts)?;
let encoder = builder.into_inner()?;
let buf = encoder.finish()?;
let file = buf.into_inner().map_err(std::io::Error::other)?;
file.sync_all()?;
Ok(())
}
#[cfg(feature = "xz2")]
pub fn compress_to_writer<W: std::io::Write>(
inputs: &[Utf8PathBuf],
writer: W,
opts: &CompressOpts<'_>,
) -> Result<()> {
let inputs = filter::validate_inputs(inputs, opts)?;
let level = validate_level(opts.level.unwrap_or(6))?;
let buf = BufWriter::new(writer);
let encoder = xz2::write::XzEncoder::new(buf, level);
let mut builder = tar::Builder::new(encoder);
builder.follow_symlinks(opts.follow_symlinks);
filter::append_inputs(&mut builder, &inputs, opts)?;
let encoder = builder.into_inner()?;
let buf = encoder.finish()?;
let mut writer = buf.into_inner().map_err(std::io::Error::from)?;
writer.flush()?;
Ok(())
}
#[cfg(not(feature = "xz2"))]
pub fn compress_to_writer<W: std::io::Write>(
inputs: &[Utf8PathBuf],
writer: W,
opts: &CompressOpts<'_>,
) -> Result<()> {
let inputs = filter::validate_inputs(inputs, opts)?;
let level = validate_level(opts.level.unwrap_or(6))?;
let encoder = lzma_rust2::XzWriter::new(writer, lzma_rust2::XzOptions::with_preset(level))?;
let mut builder = tar::Builder::new(encoder);
builder.follow_symlinks(opts.follow_symlinks);
filter::append_inputs(&mut builder, &inputs, opts)?;
let encoder = builder.into_inner()?;
let mut writer = encoder.finish()?;
writer.flush()?;
Ok(())
}
pub fn decompress(input: &Utf8Path, output: &Utf8Path, opts: &DecompressOpts<'_>) -> Result<()> {
let mut archive = open_archive(input)?;
filter::unpack_tar_filtered(&mut archive, output, opts)?;
Ok(())
}
#[cfg(feature = "xz2")]
pub fn decompress_from_reader<R: std::io::Read>(
reader: R,
output: &Utf8Path,
opts: &DecompressOpts<'_>,
) -> Result<()> {
let decoder = xz2::read::XzDecoder::new(BufReader::new(reader));
let mut archive = tar::Archive::new(decoder);
filter::unpack_tar_filtered(&mut archive, output, opts)?;
Ok(())
}
#[cfg(not(feature = "xz2"))]
pub fn decompress_from_reader<R: std::io::Read>(
reader: R,
output: &Utf8Path,
opts: &DecompressOpts<'_>,
) -> Result<()> {
let decoder = lzma_rust2::XzReader::new(BufReader::new(reader), true);
let mut archive = tar::Archive::new(decoder);
filter::unpack_tar_filtered(&mut archive, output, opts)?;
Ok(())
}
pub fn decompress_to_writer<W: std::io::Write>(
input: &Utf8Path,
writer: &mut W,
opts: &DecompressOpts<'_>,
) -> Result<()> {
let mut archive = open_archive(input)?;
filter::extract_tar_to_writer(&mut archive, writer, opts)
}
#[cfg(feature = "xz2")]
pub fn decompress_reader_to_writer<R: std::io::Read, W: std::io::Write>(
reader: R,
writer: &mut W,
opts: &DecompressOpts<'_>,
) -> Result<()> {
let decoder = xz2::read::XzDecoder::new(BufReader::new(reader));
let mut archive = tar::Archive::new(decoder);
filter::extract_tar_to_writer(&mut archive, writer, opts)
}
#[cfg(not(feature = "xz2"))]
pub fn decompress_reader_to_writer<R: std::io::Read, W: std::io::Write>(
reader: R,
writer: &mut W,
opts: &DecompressOpts<'_>,
) -> Result<()> {
let decoder = lzma_rust2::XzReader::new(BufReader::new(reader), true);
let mut archive = tar::Archive::new(decoder);
filter::extract_tar_to_writer(&mut archive, writer, opts)
}
pub fn test(input: &Utf8Path, progress: &dyn crate::progress::ProgressReport) -> Result<()> {
let mut archive = open_archive(input)?;
filter::verify_tar_entries(&mut archive, progress)
}
pub fn list(input: &Utf8Path) -> Result<Vec<Entry>> {
let mut archive = open_archive(input)?;
filter::list_tar_entries(&mut archive)
}
pub fn list_from_reader<R: std::io::Read>(reader: R) -> Result<Vec<Entry>> {
let mut archive = archive_from_reader(reader);
filter::list_tar_entries(&mut archive)
}
pub fn test_from_reader<R: std::io::Read>(
reader: R,
progress: &dyn crate::progress::ProgressReport,
) -> Result<()> {
let mut archive = archive_from_reader(reader);
filter::verify_tar_entries(&mut archive, progress)
}
pub fn info(input: &Utf8Path) -> Result<ArchiveInfo> {
let compressed_size = fs_err::metadata(input)?.len();
let mut archive = open_archive(input)?;
let (entry_count, total_uncompressed) = filter::count_tar_entries(&mut archive)?;
Ok(ArchiveInfo {
format: "tar-xz",
entry_count,
total_uncompressed,
compressed_size,
})
}
#[cfg(feature = "xz2")]
pub fn info_from_reader<R: std::io::Read>(reader: R) -> Result<ArchiveInfo> {
let counter = std::sync::Arc::new(std::sync::atomic::AtomicU64::new(0));
let counting = filter::CountingReader::new(reader, std::sync::Arc::clone(&counter));
let decoder = xz2::read::XzDecoder::new(BufReader::new(counting));
let mut archive = tar::Archive::new(decoder);
let (entry_count, total_uncompressed) = filter::count_tar_entries(&mut archive)?;
Ok(ArchiveInfo {
format: "tar-xz",
entry_count,
total_uncompressed,
compressed_size: counter.load(std::sync::atomic::Ordering::Relaxed),
})
}
#[cfg(not(feature = "xz2"))]
pub fn info_from_reader<R: std::io::Read>(reader: R) -> Result<ArchiveInfo> {
let counter = std::sync::Arc::new(std::sync::atomic::AtomicU64::new(0));
let counting = filter::CountingReader::new(reader, std::sync::Arc::clone(&counter));
let decoder = lzma_rust2::XzReader::new(BufReader::new(counting), true);
let mut archive = tar::Archive::new(decoder);
let (entry_count, total_uncompressed) = filter::count_tar_entries(&mut archive)?;
Ok(ArchiveInfo {
format: "tar-xz",
entry_count,
total_uncompressed,
compressed_size: counter.load(std::sync::atomic::Ordering::Relaxed),
})
}
#[cfg(feature = "xz2")]
fn archive_from_reader<R: std::io::Read>(
reader: R,
) -> tar::Archive<xz2::read::XzDecoder<BufReader<R>>> {
tar::Archive::new(xz2::read::XzDecoder::new(BufReader::new(reader)))
}
#[cfg(not(feature = "xz2"))]
fn archive_from_reader<R: std::io::Read>(
reader: R,
) -> tar::Archive<lzma_rust2::XzReader<BufReader<R>>> {
tar::Archive::new(lzma_rust2::XzReader::new(BufReader::new(reader), true))
}
#[cfg(feature = "xz2")]
fn open_archive(
input: &Utf8Path,
) -> Result<tar::Archive<xz2::read::XzDecoder<BufReader<fs_err::File>>>> {
let file = fs_err::File::open(input)?;
let buf = BufReader::new(file);
let decoder = xz2::read::XzDecoder::new(buf);
Ok(tar::Archive::new(decoder))
}
#[cfg(not(feature = "xz2"))]
fn open_archive(
input: &Utf8Path,
) -> Result<tar::Archive<lzma_rust2::XzReader<BufReader<fs_err::File>>>> {
let file = fs_err::File::open(input)?;
let buf = BufReader::new(file);
let decoder = lzma_rust2::XzReader::new(buf, true);
Ok(tar::Archive::new(decoder))
}