use crate::{
command::{
Command,
core::{MIN_SPLIT_PART_BYTES, write_split_archive},
},
utils::PathWithCwd,
};
use anyhow::{Context, ensure};
use bytesize::ByteSize;
use clap::{ArgGroup, Parser, ValueHint};
use pna::Archive;
use std::{borrow::Cow, fs, path::PathBuf};
#[derive(Parser, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
#[command(
group(ArgGroup::new("archive_arg").args(["file", "archive"]).required(true)),
group(ArgGroup::new("overwrite-flag").args(["overwrite", "no_overwrite"]))
)]
pub(crate) struct SplitCommand {
#[arg(short = 'f', long = "file", help = "Archive file path", value_hint = ValueHint::FilePath)]
file: Option<PathBuf>,
#[arg(value_hint = ValueHint::FilePath, hide = true)]
pub(crate) archive: Option<PathBuf>,
#[arg(long, value_name = "DIRECTORY", help = "Output directory for split archives", value_hint = ValueHint::DirPath)]
out_dir: Option<PathBuf>,
#[arg(long, help = "Overwrite file")]
overwrite: bool,
#[arg(
long,
help = "Do not overwrite files. This is the inverse option of --overwrite"
)]
no_overwrite: bool,
#[arg(
long,
value_name = "size",
help = "Maximum size in bytes of split archive (minimum 64B)"
)]
pub(crate) max_size: Option<ByteSize>,
}
impl Command for SplitCommand {
#[inline]
fn execute(self, _ctx: &crate::cli::GlobalContext) -> anyhow::Result<()> {
split_archive(self)
}
}
#[hooq::hooq(anyhow)]
fn split_archive(args: SplitCommand) -> anyhow::Result<()> {
let archive_path = match (args.file, args.archive) {
(Some(f), _) => f,
(None, Some(a)) => {
log::warn!("positional `archive` is deprecated, use `--file` instead");
a
}
_ => unreachable!("required by ArgGroup"),
};
let max_file_size = usize::try_from(args.max_size.unwrap_or_else(|| ByteSize::gb(1)).as_u64())
.context("--max-size is too large for this platform")?;
ensure!(
max_file_size >= MIN_SPLIT_PART_BYTES,
"The value for --max-size must be at least {MIN_SPLIT_PART_BYTES} bytes ({}).",
ByteSize::b(MIN_SPLIT_PART_BYTES as u64)
);
let read_file = fs::File::open(&archive_path)?;
#[cfg(not(feature = "memmap"))]
let mut read_archive = Archive::read_header(read_file)?;
#[cfg(not(feature = "memmap"))]
let entries = read_archive.raw_entries();
#[cfg(feature = "memmap")]
let mapped_file = crate::utils::mmap::Mmap::try_from(read_file)?;
#[cfg(feature = "memmap")]
let mut read_archive = Archive::read_header_from_slice(&mapped_file[..])?;
#[cfg(feature = "memmap")]
let entries = read_archive.raw_entries_slice();
let base_out_file_name = if let Some(out_dir) = args.out_dir {
fs::create_dir_all(&out_dir)?;
Cow::Owned(out_dir.join(archive_path.file_name().unwrap_or_default()))
} else {
Cow::Borrowed(archive_path.as_path())
};
write_split_archive(&base_out_file_name, entries, max_file_size, args.overwrite).with_context(
|| {
format!(
"failed to create `{}`",
PathWithCwd::new(&base_out_file_name)
)
},
)
}