Skip to content

Commit

Permalink
feat: #32 enhance archiver functionality with compression level suppo…
Browse files Browse the repository at this point in the history
…rt and refactor methods for improved clarity
  • Loading branch information
tamada committed Feb 5, 2025
1 parent 26e8bab commit 913955b
Show file tree
Hide file tree
Showing 16 changed files with 168 additions and 70 deletions.
7 changes: 4 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,16 @@ edition = "2021"
bzip2 = "0.4.4" # the newer version 0.5.0 does not work well.
cab = "0.6.0"
chrono = "0.4.39"
clap = { version = "4.5.27", features = ["derive"] }
clap_complete = "4.5.42"
clap = { version = "4.5.28", features = ["derive"] }
clap-num = "1.2.0"
clap_complete = "4.5.44"
delharc = { version = "0.6.1", features = ["lh1", "lz"] }
env_logger = "0.11.6"
flate2 = "1.0.35"
humansize = "2.1.3"
ignore = "0.4.23"
log = "0.4.25"
sevenz-rust = "0.6.0"
sevenz-rust = "0.6.1"
tar = "0.4.43"
time = "0.3.37"
typed-builder = "0.20.0"
Expand Down
34 changes: 30 additions & 4 deletions src/archiver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ pub trait ToteArchiver {
/// Perform the archiving operation.
/// - `file` is the destination file for the archive.
/// - `tps` is the list of files to be archived.
fn perform(&self, file: File, tps: Vec<TargetPath>) -> Result<Vec<ArchiveEntry>>;
fn perform(&self, file: File, tps: Targets) -> Result<Vec<ArchiveEntry>>;
/// Returns true if this archiver is enabled.
fn enable(&self) -> bool;
}
Expand Down Expand Up @@ -126,6 +126,9 @@ pub struct Archiver {
/// The list of files to be archived.
#[builder(setter(into))]
pub targets: Vec<PathBuf>,
/// The compression level (available: 0 to 9, 0 is none and 9 is finest).
#[builder(default = 5)]
pub level: u8,
/// the prefix directory for the each file into the archive files when `Some`
#[builder(default = None, setter(strip_option, into))]
pub rebase_dir: Option<PathBuf>,
Expand All @@ -141,6 +144,25 @@ pub struct Archiver {
pub ignore_types: Vec<IgnoreType>,
}

pub struct Targets<'a> {
paths: Vec<TargetPath<'a>>,
opts: &'a Archiver,
}

impl<'a> Targets<'a> {
fn new(paths: Vec<TargetPath<'a>>, opts: &'a Archiver) -> Self {
Self { paths, opts }
}

pub(crate) fn iter(&self) -> impl Iterator<Item = &TargetPath<'a>> {
self.paths.iter()
}

pub(crate) fn level(&self) -> u8 {
self.opts.level
}
}

/// TargetPath is a helper struct to handle the target path for the archiving operation.
pub struct TargetPath<'a> {
base_path: &'a PathBuf,
Expand Down Expand Up @@ -171,8 +193,12 @@ impl<'a> TargetPath<'a> {
}
}

pub fn iter(&self) -> impl Iterator<Item = ignore::DirEntry> {
self.walker().flatten()
}

/// Returns the directory traversing walker for the given path of this instance.
pub fn walker(&self) -> Walk {
fn walker(&self) -> Walk {
let mut builder = WalkBuilder::new(self.base_path);
build_walker_impl(self.opts, &mut builder);
builder.build()
Expand Down Expand Up @@ -216,7 +242,7 @@ impl Archiver {
}
}
}
match self.archive_impl(archiver, paths) {
match self.archive_impl(archiver, Targets::new(paths, self)) {
Ok(entries) => {
let compressed = self.archive_file.metadata().map(|m| m.len()).unwrap_or(0);
Ok(ArchiveEntries::new(&self.archive_file, entries, compressed))
Expand All @@ -228,7 +254,7 @@ impl Archiver {
fn archive_impl(
&self,
archiver: Box<dyn ToteArchiver>,
tps: Vec<TargetPath>,
tps: Targets,
) -> Result<Vec<ArchiveEntry>> {
match File::create(&self.archive_file) {
Ok(f) => archiver.perform(f, tps),
Expand Down
20 changes: 14 additions & 6 deletions src/archiver/cab.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@ use std::path::PathBuf;

use cab::{CabinetBuilder, CabinetWriter};

use crate::archiver::{ArchiveEntry, TargetPath, ToteArchiver};
use crate::archiver::{ArchiveEntry, TargetPath, Targets, ToteArchiver};
use crate::{Result, ToteError};

pub(super) struct CabArchiver {}

impl ToteArchiver for CabArchiver {
fn perform(&self, file: File, tps: Vec<TargetPath>) -> Result<Vec<ArchiveEntry>> {
fn perform(&self, file: File, tps: Targets) -> Result<Vec<ArchiveEntry>> {
let mut errs = vec![];
let mut entries = vec![];
let mut builder = CabinetBuilder::new();
let folder = builder.add_folder(cab::CompressionType::MsZip);
let ctype = compression_type(tps.level());
let folder = builder.add_folder(ctype);
let list = collect_entries(&tps);
for (path, tp) in list.clone() {
entries.push(ArchiveEntry::from(&path));
Expand All @@ -39,6 +40,13 @@ impl ToteArchiver for CabArchiver {
}
}

fn compression_type(level: u8) -> cab::CompressionType {
match level {
0 => cab::CompressionType::None,
_ => cab::CompressionType::MsZip,
}
}

fn write_entry(writer: &mut CabinetWriter<File>, path: PathBuf) -> Result<()> {
match (File::open(path), writer.next_file()) {
(Ok(mut reader), Ok(Some(mut w))) => match std::io::copy(&mut reader, &mut w) {
Expand All @@ -55,10 +63,10 @@ fn write_entry(writer: &mut CabinetWriter<File>, path: PathBuf) -> Result<()> {
}
}

fn collect_entries<'a>(tps: &'a Vec<TargetPath>) -> Vec<(PathBuf, &'a TargetPath<'a>)> {
fn collect_entries<'a>(tps: &'a Targets) -> Vec<(PathBuf, &'a TargetPath<'a>)> {
let mut r = vec![];
for tp in tps {
for t in tp.walker().flatten() {
for tp in tps.iter() {
for t in tp.iter() {
let path = t.into_path();
if path.is_file() {
r.push((path, tp));
Expand Down
6 changes: 2 additions & 4 deletions src/archiver/lha.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
use std::fs::File;

use crate::archiver::{ArchiveEntry, ToteArchiver};
use crate::archiver::{ArchiveEntry, Targets, ToteArchiver};
use crate::{Result, ToteError};

use super::TargetPath;

pub(super) struct LhaArchiver {}

impl ToteArchiver for LhaArchiver {
fn perform(&self, _: File, _: Vec<TargetPath>) -> Result<Vec<ArchiveEntry>> {
fn perform(&self, _: File, _: Targets) -> Result<Vec<ArchiveEntry>> {
Err(ToteError::UnsupportedFormat(
"only extraction support for lha".to_string(),
))
Expand Down
22 changes: 19 additions & 3 deletions src/archiver/os.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,26 @@ pub(super) mod windows;

pub(super) mod linux;

pub(super) fn create_file_opts(target: &PathBuf) -> SimpleFileOptions {
pub(super) fn create_file_opts(target: &PathBuf, level: i64) -> SimpleFileOptions {
if cfg!(target_os = "windows") {
windows::create_file_opts(target)
windows::create_file_opts(target, level)
} else {
linux::create_file_opts(target)
linux::create_file_opts(target, level)
}
}

pub(crate) fn method_and_level(level: i64) -> (zip::CompressionMethod, Option<i64>) {
match level {
0 => (zip::CompressionMethod::Stored, None),
1 => (zip::CompressionMethod::Deflated, Some(10)),
2 => (zip::CompressionMethod::Deflated, Some(24)),
3 => (zip::CompressionMethod::Deflated, Some(264)),
4 => (zip::CompressionMethod::Bzip2, Some(1)),
5 => (zip::CompressionMethod::Bzip2, Some(6)),
6 => (zip::CompressionMethod::Bzip2, Some(9)),
7 => (zip::CompressionMethod::Zstd, Some(-7)),
8 => (zip::CompressionMethod::Zstd, Some(3)),
9 => (zip::CompressionMethod::Zstd, Some(22)),
_ => (zip::CompressionMethod::Deflated, Some(6)),
}
}
6 changes: 4 additions & 2 deletions src/archiver/os/linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ use time::OffsetDateTime;
use zip::write::SimpleFileOptions;
use zip::DateTime;

pub(super) fn create_file_opts(target: &PathBuf) -> SimpleFileOptions {
pub(super) fn create_file_opts(target: &PathBuf, level: i64) -> SimpleFileOptions {
let metadata = std::fs::metadata(target).unwrap();
let mod_time = DateTime::try_from(OffsetDateTime::from(metadata.modified().unwrap()));
let (method, level) = super::method_and_level(level);

SimpleFileOptions::default()
.last_modified_time(mod_time.unwrap())
.compression_method(zip::CompressionMethod::Stored)
.compression_method(method)
.compression_level(level)
.unix_permissions(metadata.permissions().mode())
}
6 changes: 4 additions & 2 deletions src/archiver/os/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ use zip::DateTime;

use std::path::PathBuf;

pub(super) fn create_file_opts(target: &PathBuf) -> SimpleFileOptions {
pub(super) fn create_file_opts(target: &PathBuf, level: i64) -> SimpleFileOptions {
let metadata = std::fs::metadata(target).unwrap();
let mod_time = DateTime::try_from(OffsetDateTime::from(metadata.modified().unwrap()));
let (method, level) = super::method_and_level(level);

SimpleFileOptions::default()
.last_modified_time(mod_time.unwrap())
.compression_method(zip::CompressionMethod::Stored)
.compression_method(method)
.compression_level(level)
}
6 changes: 2 additions & 4 deletions src/archiver/rar.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
use std::fs::File;

use crate::archiver::{ArchiveEntry, ToteArchiver};
use crate::archiver::{ArchiveEntry, Targets, ToteArchiver};
use crate::{Result, ToteError};

use super::TargetPath;

pub(super) struct RarArchiver {}

impl ToteArchiver for RarArchiver {
fn perform(&self, _: File, _: Vec<TargetPath>) -> Result<Vec<ArchiveEntry>> {
fn perform(&self, _: File, _: Targets) -> Result<Vec<ArchiveEntry>> {
Err(ToteError::UnsupportedFormat(
"only extraction support for rar".to_string(),
))
Expand Down
24 changes: 17 additions & 7 deletions src/archiver/sevenz.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
use std::fs::File;
use std::path::PathBuf;

use sevenz_rust::{SevenZArchiveEntry, SevenZWriter};
use sevenz_rust::{SevenZArchiveEntry, SevenZMethod, SevenZMethodConfiguration, SevenZWriter};

use crate::archiver::{ArchiveEntry, TargetPath, ToteArchiver};
use crate::archiver::{ArchiveEntry, Targets, ToteArchiver};
use crate::{Result, ToteError};

pub(super) struct SevenZArchiver {}

impl ToteArchiver for SevenZArchiver {
fn perform(&self, file: File, tps: Vec<TargetPath>) -> Result<Vec<ArchiveEntry>> {
fn perform(&self, file: File, tps: Targets) -> Result<Vec<ArchiveEntry>> {
let mut w = match SevenZWriter::new(file) {
Ok(writer) => writer,
Err(e) => return Err(ToteError::Archiver(e.to_string())),
};
set_compression_level(&mut w, tps.level());
let mut errs = vec![];
let mut entries = vec![];
for tp in tps {
for tp in tps.iter() {
for t in tp.walker().flatten() {
let path = t.into_path();
entries.push(ArchiveEntry::from(&path));
Expand All @@ -42,6 +43,14 @@ impl ToteArchiver for SevenZArchiver {
}
}

fn set_compression_level(szw: &mut SevenZWriter<File>, level: u8) {
let level = match level {
0..=4 => SevenZMethod::LZMA,
_ => SevenZMethod::LZMA2,
};
szw.set_content_methods(vec![SevenZMethodConfiguration::new(level)]);
}

fn process_file(szw: &mut SevenZWriter<File>, target: &PathBuf, dest_path: &PathBuf) -> Result<()> {
let name = &dest_path.to_str().unwrap();
if let Err(e) = szw.push_archive_entry(
Expand Down Expand Up @@ -72,15 +81,16 @@ mod tests {
}

#[test]
fn test_zip() {
fn test_sevenz() {
run_test(|| {
let archiver = Archiver::builder()
.archive_file(PathBuf::from("results/test.7z"))
.targets(vec![PathBuf::from("src"), PathBuf::from("Cargo.toml")])
.overwrite(true)
.build();
let result = archiver.perform();
assert!(result.is_ok());
if let Err(e) = archiver.perform() {
panic!("{:?}", e);
}
});
}

Expand Down
31 changes: 18 additions & 13 deletions src/archiver/tar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::path::PathBuf;
use tar::Builder;
use xz2::write::XzEncoder;

use crate::archiver::{ArchiveEntry, TargetPath, ToteArchiver};
use crate::archiver::{ArchiveEntry, Targets, ToteArchiver};
use crate::{Result, ToteError};

pub(super) struct TarArchiver {}
Expand All @@ -16,7 +16,7 @@ pub(super) struct TarXzArchiver {}
pub(super) struct TarZstdArchiver {}

impl ToteArchiver for TarArchiver {
fn perform(&self, file: File, tps: Vec<TargetPath>) -> Result<Vec<ArchiveEntry>> {
fn perform(&self, file: File, tps: Targets) -> Result<Vec<ArchiveEntry>> {
write_tar(tps, file)
}
fn enable(&self) -> bool {
Expand All @@ -25,48 +25,53 @@ impl ToteArchiver for TarArchiver {
}

impl ToteArchiver for TarGzArchiver {
fn perform(&self, file: File, tps: Vec<TargetPath>) -> Result<Vec<ArchiveEntry>> {
write_tar(tps, GzEncoder::new(file, flate2::Compression::default()))
fn perform(&self, file: File, tps: Targets) -> Result<Vec<ArchiveEntry>> {
let level = tps.level() as u32;
write_tar(tps, GzEncoder::new(file, flate2::Compression::new(level)))
}
fn enable(&self) -> bool {
true
}
}

impl ToteArchiver for TarBz2Archiver {
fn perform(&self, file: File, tps: Vec<TargetPath>) -> Result<Vec<ArchiveEntry>> {
write_tar(tps, BzEncoder::new(file, bzip2::Compression::best()))
fn perform(&self, file: File, tps: Targets) -> Result<Vec<ArchiveEntry>> {
let level = tps.level() as u32;
write_tar(tps, BzEncoder::new(file, bzip2::Compression::new(level)))
}
fn enable(&self) -> bool {
true
}
}

impl ToteArchiver for TarXzArchiver {
fn perform(&self, file: File, tps: Vec<TargetPath>) -> Result<Vec<ArchiveEntry>> {
write_tar(tps, XzEncoder::new(file, 9))
fn perform(&self, file: File, tps: Targets) -> Result<Vec<ArchiveEntry>> {
let level = tps.level() as u32;
write_tar(tps, XzEncoder::new(file, level))
}
fn enable(&self) -> bool {
true
}
}

impl ToteArchiver for TarZstdArchiver {
fn perform(&self, file: File, tps: Vec<TargetPath>) -> Result<Vec<ArchiveEntry>> {
let encoder = zstd::Encoder::new(file, 9).unwrap();
fn perform(&self, file: File, tps: Targets) -> Result<Vec<ArchiveEntry>> {
let level = tps.level() as u32;
let level = (level as f64 + 1.0) / 10.0 * 22.0; // convert to 1-22
let encoder = zstd::Encoder::new(file, level as i32).unwrap();
write_tar(tps, encoder.auto_finish())
}
fn enable(&self) -> bool {
true
}
}

fn write_tar<W: Write>(tps: Vec<TargetPath>, f: W) -> Result<Vec<ArchiveEntry>> {
fn write_tar<W: Write>(tps: Targets, f: W) -> Result<Vec<ArchiveEntry>> {
let mut builder = tar::Builder::new(f);
let mut errs = vec![];
let mut entries = vec![];
for tp in tps {
for t in tp.walker().flatten() {
for tp in tps.iter() {
for t in tp.iter() {
let path = t.into_path();
entries.push(ArchiveEntry::from(&path));
let dest_dir = tp.dest_path(&path);
Expand Down
Loading

0 comments on commit 913955b

Please sign in to comment.