diff --git a/.github/workflows/ci-version.yml b/.github/workflows/ci-version.yml index 5d2d07e..eb523d8 100644 --- a/.github/workflows/ci-version.yml +++ b/.github/workflows/ci-version.yml @@ -85,7 +85,7 @@ jobs: os: - macos-latest toolchain: - - "1.60" + - "1.70" features: - name: Test ${{ matrix.toolchain }} on ${{ matrix.os }} (${{ matrix.features }}) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 46a165d..fb6c589 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -78,7 +78,7 @@ jobs: os: - ubuntu-latest toolchain: - - "1.60" + - "1.70" target: - x86_64-unknown-linux-gnu - x86_64-unknown-linux-musl diff --git a/Cargo.toml b/Cargo.toml index 6b433cd..301cc2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "xcompress" -version = "0.11.8" +version = "0.12.0" authors = ["Magic Len "] edition = "2021" -rust-version = "1.60" +rust-version = "1.70" repository = "https://github.com/magiclen/xcompress" homepage = "https://magiclen.org/xcompress" keywords = ["compression", "decompression", "zip", "archive", "tar"] @@ -19,13 +19,15 @@ panic = "abort" strip = true [dependencies] -clap = "3.2.23" +clap = { version = "4", features = ["derive"] } concat-with = "0.2" -terminal_size = "0.2" +terminal_size = "0.3" -execute = "0.2.4" -num_cpus = "1.8.0" -scanner-rust = "2.0.9" +anyhow = "1" + +execute = "0.2" +num_cpus = "1" +scanner-rust = "2" [dependencies.path-absolutize] version = "3" diff --git a/README.md b/README.md index a8cf4e0..b0af6a8 100644 --- a/README.md +++ b/README.md @@ -18,45 +18,44 @@ xcompress x foo.rar # Extract foo.rar into current working xcompress x foo.tar.gz /tmp/out_folder # Extract foo.tar.gz into /tmp/out_folder xcompress x -p password foo.rar # Extract foo.rar with a password into current working directory -USAGE: - xcompress [OPTIONS] [SUBCOMMAND] - -OPTIONS: - -p, --password Set password for your archive file. (Only supports 7Z, ZIP and RAR.) Set an empty string to read a password from stdin. - --7z-path <7Z_PATH> Specify the path of your 7z executable binary file. [default: 7z] - --bunzip2-path Specify the path of your bunzip2 executable binary file. [default: bunzip2] - --bzip2-path Specify the path of your bzip2 executable binary file. [default: bzip2] - --compress-path Specify the path of your compress executable binary file. [default: compress] - --gunzip-path Specify the path of your gunzip executable binary file. [default: gunzip] - --gzip-path Specify the path of your gzip executable binary file. [default: gzip] - -h, --help Print help information - --lbzip2-path Specify the path of your lbzip2 executable binary file. [default: lbzip2] - --lunzip-path Specify the path of your lunzip executable binary file. [default: lunzip] - --lzip-path Specify the path of your lzip executable binary file. [default: lzip] - --lzma-path Specify the path of your lzma executable binary file. [default: lzma] - --pbzip2-path Specify the path of your pbzip2 executable binary file. [default: pbzip2] - --pigz-path Specify the path of your pigz executable binary file. [default: pigz] - --plzip-path Specify the path of your plzip executable binary file. [default: plzip] - --pxz-path Specify the path of your pxz executable binary file. [default: pxz] - --pzstd-path Specify the path of your pzstd executable binary file. [default: pzstd] - -q, --quiet Make programs not print anything on the screen. - --rar-path Specify the path of your rar executable binary file. [default: rar] - -s, --single-thread Use only one thread. - --tar-path Specify the path of your tar executable binary file. [default: tar] - --unlzma-path Specify the path of your unlzma executable binary file. [default: unlzma] - --unrar-path Specify the path of your unrar executable binary file. [default: unrar] - --unxz-path Specify the path of your unxz executable binary file. [default: unxz] - --unzip-path Specify the path of your unzip executable binary file. [default: unzip] - --unzstd-path Specify the path of your unzstd executable binary file. [default: unzstd] - -V, --version Print version information - --xz-path Specify the path of your xz executable binary file. [default: xz] - --zip-path Specify the path of your zip executable binary file. [default: zip] - --zstd-path Specify the path of your zstd executable binary file. [default: zstd] - -SUBCOMMANDS: - a Add files to archive. Excludes base directory from names. (e.g. add /path/to/folder, you can always get the "folder" in the root of the archive file, instead of /path/to/folder.) - help Print this message or the help of the given subcommand(s) - x Extract files with full path. +Usage: xcompress [OPTIONS] + +Commands: + x Extract files with full path + a Add files to archive. Excludes base directory from names (e.g. add /path/to/folder, you can always get the "folder" in the root of the archive file, instead of /path/to/folder) + help Print this message or the help of the given subcommand(s) + +Options: + -q, --quiet Make programs not print anything on the screen + -s, --single-thread Use only one thread + -p, --password Set password for your archive file. (Only supports 7Z, ZIP and RAR) Set an empty string to read a password from stdin + --compress-path Specify the path of your compress executable binary file [default: compress] + --zip-path Specify the path of your zip executable binary file [default: zip] + --unzip-path Specify the path of your unzip executable binary file [default: unzip] + --gzip-path Specify the path of your gzip executable binary file [default: gzip] + --gnuzip-path Specify the path of your gunzip executable binary file [default: gunzip] + --pigz-path Specify the path of your pigz executable binary file [default: pigz] + --bzip2-path Specify the path of your bzip2 executable binary file [default: bzip2] + --bunzip2-path Specify the path of your bunzip2 executable binary file [default: bunzip2] + --lbzip2-path Specify the path of your lbzip2 executable binary file [default: lbzip2] + --pbzip2-path Specify the path of your pbzip2 executable binary file [default: pbzip2] + --lzip-path Specify the path of your lzip executable binary file [default: lzip] + --lunzip-path Specify the path of your lunzip executable binary file [default: lunzip] + --plzip-path Specify the path of your plzip executable binary file [default: plzip] + --xz-path Specify the path of your xz executable binary file [default: xz] + --unxz-path Specify the path of your unxz executable binary file [default: unxz] + --pxz-path Specify the path of your pxz executable binary file [default: pxz] + --lzma-path Specify the path of your lzma executable binary file [default: lzma] + --unlzma-path Specify the path of your unlzma executable binary file [default: unlzma] + --7Z_PATH Specify the path of your 7z executable binary file [default: 7z] + --tar-path Specify the path of your tar executable binary file [default: tar] + --rar-path Specify the path of your rar executable binary file [default: rar] + --unrar-path Specify the path of your unrar executable binary file [default: unrar] + --zstd-path Specify the path of your zstd executable binary file [default: zstd] + --unzstd-path Specify the path of your unzstd executable binary file [default: unzstd] + --pzstd-path Specify the path of your pzstd executable binary file [default: pzstd] + -h, --help Print help + -V, --version Print version ``` ## License diff --git a/src/archive_format.rs b/src/archive_format.rs new file mode 100644 index 0000000..f9787d1 --- /dev/null +++ b/src/archive_format.rs @@ -0,0 +1,85 @@ +use std::path::Path; + +use anyhow::anyhow; + +#[derive(Debug)] +pub enum ArchiveFormat { + Z, + Zip, + Gzip, + Bzip2, + Lz, + Xz, + Lzma, + P7z, + Tar, + TarZ, + TarGzip, + TarBzip2, + TarLz, + TarXz, + TarLzma, + Tar7z, + TarZstd, + Rar, + Zstd, +} + +impl ArchiveFormat { + pub fn get_archive_format_from_file_path>( + file_path: P, + ) -> anyhow::Result { + let file_path = file_path.as_ref(); + + if let Some(file_name) = file_path.file_name() { + if let Some(file_name) = file_name.to_str() { + let file_name = file_name.to_ascii_lowercase(); + + if file_name.ends_with("tar.z") { + return Ok(ArchiveFormat::TarZ); + } else if file_name.ends_with(".tar.gz") || file_name.ends_with(".tgz") { + return Ok(ArchiveFormat::TarGzip); + } else if file_name.ends_with(".tar.bz2") || file_name.ends_with(".tbz2") { + return Ok(ArchiveFormat::TarBzip2); + } else if file_name.ends_with(".tar.lz") { + return Ok(ArchiveFormat::TarLz); + } else if file_name.ends_with(".tar.xz") || file_name.ends_with(".txz") { + return Ok(ArchiveFormat::TarXz); + } else if file_name.ends_with(".tar.lzma") || file_name.ends_with(".tlz") { + return Ok(ArchiveFormat::TarLzma); + } else if file_name.ends_with(".tar.7z") + || file_name.ends_with(".tar.7z.001") + || file_name.ends_with(".t7z") + { + return Ok(ArchiveFormat::Tar7z); + } else if file_name.ends_with(".tar.zst") { + return Ok(ArchiveFormat::TarZstd); + } else if file_name.ends_with(".tar") { + return Ok(ArchiveFormat::Tar); + } else if file_name.ends_with(".z") { + return Ok(ArchiveFormat::Z); + } else if file_name.ends_with(".zip") { + return Ok(ArchiveFormat::Zip); + } else if file_name.ends_with(".gz") { + return Ok(ArchiveFormat::Gzip); + } else if file_name.ends_with(".bz2") { + return Ok(ArchiveFormat::Bzip2); + } else if file_name.ends_with(".lz") { + return Ok(ArchiveFormat::Lz); + } else if file_name.ends_with(".xz") { + return Ok(ArchiveFormat::Xz); + } else if file_name.ends_with(".lzma") { + return Ok(ArchiveFormat::Lzma); + } else if file_name.ends_with(".7z") || file_name.ends_with(".7z.001") { + return Ok(ArchiveFormat::P7z); + } else if file_name.ends_with(".rar") { + return Ok(ArchiveFormat::Rar); + } else if file_name.ends_with(".zst") { + return Ok(ArchiveFormat::Zstd); + } + } + } + + Err(anyhow!("Unknown archive format.")) + } +} diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..cf741c7 --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,309 @@ +use std::path::PathBuf; + +use clap::{Args, CommandFactory, FromArgMatches, Parser, Subcommand}; +use concat_with::concat_line; +use terminal_size::terminal_size; + +const APP_NAME: &str = "XCompress"; +const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); +const CARGO_PKG_AUTHORS: &str = env!("CARGO_PKG_AUTHORS"); + +const AFTER_HELP: &str = "Enjoy it! https://magiclen.org"; + +const APP_ABOUT: &str = concat!( + "XCompress is a free file archiver utility on Linux, providing multi-format archiving to and \ + extracting from ZIP, Z, GZIP, BZIP2, LZ, XZ, LZMA, 7ZIP, TAR and RAR.\n\nEXAMPLES:\n", + concat_line!(prefix "xcompress ", + "a foo.wav # Archive foo.wav to foo.rar", + "a foo.wav /root/bar.txt # Archive foo.wav and /root/bar.txt to foo.rar", + "a -o /tmp/out.7z foo.wav # Archive foo.wav to /tmp/out.7z", + "a -b foo/bar # Archive foo/bar folder to bar.rar as small as possible", + "a -f foo/bar -r 5 # Archive foo/bar folder to bar.rar as fast as possible and add 5% recovery record", + "a -p password foo.wav # Archive foo.wav to foo.rar with a password", + "x foo.rar # Extract foo.rar into current working directory", + "x foo.tar.gz /tmp/out_folder # Extract foo.tar.gz into /tmp/out_folder", + "x -p password foo.rar # Extract foo.rar with a password into current working directory" + ) +); + +const DEFAULT_COMPRESS_PATH: &str = "compress"; +const DEFAULT_ZIP_PATH: &str = "zip"; +const DEFAULT_UNZIP_PATH: &str = "unzip"; +const DEFAULT_GZIP_PATH: &str = "gzip"; +const DEFAULT_GUNZIP_PATH: &str = "gunzip"; +const DEFAULT_PIGZ_PATH: &str = "pigz"; +const DEFAULT_BZIP2_PATH: &str = "bzip2"; +const DEFAULT_BUNZIP2_PATH: &str = "bunzip2"; +const DEFAULT_LBZIP2_PATH: &str = "lbzip2"; +const DEFAULT_PBZIP2_PATH: &str = "pbzip2"; +const DEFAULT_LZIP_PATH: &str = "lzip"; +const DEFAULT_LUNZIP_PATH: &str = "lunzip"; +const DEFAULT_PLZIP_PATH: &str = "plzip"; +const DEFAULT_XZ_PATH: &str = "xz"; +const DEFAULT_UNXZ_PATH: &str = "unxz"; +const DEFAULT_PXZ_PATH: &str = "pxz"; +const DEFAULT_LZMA_PATH: &str = "lzma"; +const DEFAULT_UNLZMA_PATH: &str = "unlzma"; +const DEFAULT_7Z_PATH: &str = "7z"; +const DEFAULT_TAR_PATH: &str = "tar"; +const DEFAULT_RAR_PATH: &str = "rar"; +const DEFAULT_UNRAR_PATH: &str = "unrar"; +const DEFAULT_ZSTD_PATH: &str = "zstd"; +const DEFAULT_UNZSTD_PATH: &str = "unzstd"; +const DEFAULT_PZSTD_PATH: &str = "pzstd"; + +#[derive(Debug, Parser)] +#[command(name = APP_NAME)] +#[command(term_width = terminal_size().map(|(width, _)| width.0 as usize).unwrap_or(0))] +#[command(version = CARGO_PKG_VERSION)] +#[command(author = CARGO_PKG_AUTHORS)] +#[command(after_help = AFTER_HELP)] +pub struct CLIArgs { + #[command(subcommand)] + pub command: CLICommands, + + #[arg(short, long)] + #[arg(global = true)] + #[arg(help = "Make programs not print anything on the screen")] + pub quiet: bool, + + #[arg(short, long)] + #[arg(global = true)] + #[arg(help = "Use only one thread")] + pub single_thread: bool, + + #[arg(short, long)] + #[arg(global = true)] + #[arg(default_missing_value = "")] + #[arg(help = "Set password for your archive file. (Only supports 7Z, ZIP and RAR) Set an \ + empty string to read a password from stdin")] + pub password: Option, + + #[command(flatten)] + pub executable_paths: ExecutablePaths, +} + +#[derive(Debug, Args)] +pub struct ExecutablePaths { + #[arg(long)] + #[arg(global = true)] + #[arg(default_value = DEFAULT_COMPRESS_PATH)] + #[arg(help = "Specify the path of your compress executable binary file")] + pub compress_path: String, + + #[arg(long)] + #[arg(global = true)] + #[arg(default_value = DEFAULT_ZIP_PATH)] + #[arg(help = "Specify the path of your zip executable binary file")] + pub zip_path: String, + + #[arg(long)] + #[arg(global = true)] + #[arg(default_value = DEFAULT_UNZIP_PATH)] + #[arg(help = "Specify the path of your unzip executable binary file")] + pub unzip_path: String, + + #[arg(long)] + #[arg(global = true)] + #[arg(default_value = DEFAULT_GZIP_PATH)] + #[arg(help = "Specify the path of your gzip executable binary file")] + pub gzip_path: String, + + #[arg(long)] + #[arg(global = true)] + #[arg(default_value = DEFAULT_GUNZIP_PATH)] + #[arg(help = "Specify the path of your gunzip executable binary file")] + pub gnuzip_path: String, + + #[arg(long)] + #[arg(global = true)] + #[arg(default_value = DEFAULT_PIGZ_PATH)] + #[arg(help = "Specify the path of your pigz executable binary file")] + pub pigz_path: String, + + #[arg(long)] + #[arg(global = true)] + #[arg(default_value = DEFAULT_BZIP2_PATH)] + #[arg(help = "Specify the path of your bzip2 executable binary file")] + pub bzip2_path: String, + + #[arg(long)] + #[arg(global = true)] + #[arg(default_value = DEFAULT_BUNZIP2_PATH)] + #[arg(help = "Specify the path of your bunzip2 executable binary file")] + pub bunzip2_path: String, + + #[arg(long)] + #[arg(global = true)] + #[arg(default_value = DEFAULT_LBZIP2_PATH)] + #[arg(help = "Specify the path of your lbzip2 executable binary file")] + pub lbzip2_path: String, + + #[arg(long)] + #[arg(global = true)] + #[arg(default_value = DEFAULT_PBZIP2_PATH)] + #[arg(help = "Specify the path of your pbzip2 executable binary file")] + pub pbzip2_path: String, + + #[arg(long)] + #[arg(global = true)] + #[arg(default_value = DEFAULT_LZIP_PATH)] + #[arg(help = "Specify the path of your lzip executable binary file")] + pub lzip_path: String, + + #[arg(long)] + #[arg(global = true)] + #[arg(default_value = DEFAULT_LUNZIP_PATH)] + #[arg(help = "Specify the path of your lunzip executable binary file")] + pub lunzip_path: String, + + #[arg(long)] + #[arg(global = true)] + #[arg(default_value = DEFAULT_PLZIP_PATH)] + #[arg(help = "Specify the path of your plzip executable binary file")] + pub plzip_path: String, + + #[arg(long)] + #[arg(global = true)] + #[arg(default_value = DEFAULT_XZ_PATH)] + #[arg(help = "Specify the path of your xz executable binary file")] + pub xz_path: String, + + #[arg(long)] + #[arg(global = true)] + #[arg(default_value = DEFAULT_UNXZ_PATH)] + #[arg(help = "Specify the path of your unxz executable binary file")] + pub unxz_path: String, + + #[arg(long)] + #[arg(global = true)] + #[arg(default_value = DEFAULT_PXZ_PATH)] + #[arg(help = "Specify the path of your pxz executable binary file")] + pub pxz_path: String, + + #[arg(long)] + #[arg(global = true)] + #[arg(default_value = DEFAULT_LZMA_PATH)] + #[arg(help = "Specify the path of your lzma executable binary file")] + pub lzma_path: String, + + #[arg(long)] + #[arg(global = true)] + #[arg(default_value = DEFAULT_UNLZMA_PATH)] + #[arg(help = "Specify the path of your unlzma executable binary file")] + pub unlzma_path: String, + + #[arg(long = "7Z_PATH")] + #[arg(global = true)] + #[arg(default_value = DEFAULT_7Z_PATH)] + #[arg(help = "Specify the path of your 7z executable binary file")] + pub p7z_path: String, + + #[arg(long)] + #[arg(global = true)] + #[arg(default_value = DEFAULT_TAR_PATH)] + #[arg(help = "Specify the path of your tar executable binary file")] + pub tar_path: String, + + #[arg(long)] + #[arg(global = true)] + #[arg(default_value = DEFAULT_RAR_PATH)] + #[arg(help = "Specify the path of your rar executable binary file")] + pub rar_path: String, + + #[arg(long)] + #[arg(global = true)] + #[arg(default_value = DEFAULT_UNRAR_PATH)] + #[arg(help = "Specify the path of your unrar executable binary file")] + pub unrar_path: String, + + #[arg(long)] + #[arg(global = true)] + #[arg(default_value = DEFAULT_ZSTD_PATH)] + #[arg(help = "Specify the path of your zstd executable binary file")] + pub zstd_path: String, + + #[arg(long)] + #[arg(global = true)] + #[arg(default_value = DEFAULT_UNZSTD_PATH)] + #[arg(help = "Specify the path of your unzstd executable binary file")] + pub unzstd_path: String, + + #[arg(long)] + #[arg(global = true)] + #[arg(default_value = DEFAULT_PZSTD_PATH)] + #[arg(help = "Specify the path of your pzstd executable binary file")] + pub pzstd_path: String, +} + +#[derive(Debug, Subcommand)] +pub enum CLICommands { + #[command(about = "Extract files with full path")] + #[command(after_help = AFTER_HELP)] + X { + #[arg( + help = "Assign the source of your original files. It should be at least one file path" + )] + input_path: PathBuf, + #[arg(help = "Assign a destination of your extracted files. It should be a directory path")] + #[arg(conflicts_with = "output")] + output_path: Option, + #[arg(short, long)] + #[arg(conflicts_with = "output_path")] + #[arg(help = "Assign a destination of your extracted files. It should be a directory path")] + output: Option, + }, + #[command(about = "Add files to archive. Excludes base directory from names (e.g. add \ + /path/to/folder, you can always get the \"folder\" in the root of the \ + archive file, instead of /path/to/folder)")] + #[command(after_help = AFTER_HELP)] + A { + #[arg( + help = "Assign the source of your original files. It should be at least one file path" + )] + input_paths: Vec, + #[arg(short, long)] + #[arg(help = "Assign a destination of your extracted files. It should be a file path. \ + Specify the file extension name in order to determine which archive \ + format you want to use. [default archive format: RAR]")] + output_path: Option, + #[arg(short, long, visible_alias = "best")] + #[arg(conflicts_with = "fastest_compression")] + #[arg(help = "If you are OK about the compression and depression time and want to save \ + more disk space and network traffic, it will make the archive file as \ + small as possible")] + best_compression: bool, + #[arg(short, long, alias = "fast-compression", visible_alias = "fast")] + #[arg(conflicts_with = "best_compression")] + #[arg(help = "If you are OK about using more disk space and network traffic, and want \ + to get the fastest compression and depression time, it will make the \ + compression as minimal as possible (even not use compression at all)")] + fastest_compression: bool, + #[arg(short = 'd', long)] + #[arg(help = "Split the archive file into volumes with a specified size. The unit of \ + value is byte. You can also use KB, MB, KiB, MiB, etc, as a suffix. The \ + minimum volume is 64 KiB (Only supports 7Z, ZIP and RAR)")] + split: Option, + #[arg(short, long = "recovery-record", visible_alias = "rr")] + #[arg(value_parser = clap::value_parser!(u8).range(1..=100))] + #[arg(help = "Add data recovery record (Only supports RAR)")] + recovery_record: Option, + }, +} + +pub fn get_args() -> CLIArgs { + let args = CLIArgs::command(); + + let about = format!("{APP_NAME} {CARGO_PKG_VERSION}\n{CARGO_PKG_AUTHORS}\n{APP_ABOUT}"); + + let args = args.about(about); + + let matches = args.get_matches(); + + match CLIArgs::from_arg_matches(&matches) { + Ok(args) => args, + Err(err) => { + err.exit(); + }, + } +} diff --git a/src/commands/compression.rs b/src/commands/compression.rs new file mode 100644 index 0000000..b077bf1 --- /dev/null +++ b/src/commands/compression.rs @@ -0,0 +1,1710 @@ +use std::{borrow::Cow, fs, fs::File, io, process}; + +use anyhow::{anyhow, Context}; +use byte_unit::{Byte, ByteUnit}; +use execute::{command_args, Execute}; +use path_absolutize::{Absolutize, CWD}; + +use super::{read_password, try_delete_file}; +use crate::{ + archive_format::ArchiveFormat, + cli::{CLIArgs, CLICommands}, +}; + +#[derive(Debug, Eq, PartialEq)] +enum CompressionLevel { + Default, + Best, + Fast, +} + +pub fn handle_compression(cli_args: CLIArgs) -> anyhow::Result<()> { + debug_assert!(matches!(cli_args.command, CLICommands::A { .. })); + + if let CLICommands::A { + mut input_paths, + output_path, + best_compression, + fastest_compression, + split, + recovery_record, + } = cli_args.command + { + for input_path in input_paths.iter_mut() { + match input_path.canonicalize() { + Ok(path) => { + *input_path = path; + }, + Err(error) => { + return Err(error) + .with_context(|| format!("{:?}", input_path.absolutize().unwrap())); + }, + } + } + + let output_path = match output_path { + Some(output_path) => output_path, + None => { + CWD.join(format!("{}.rar", input_paths[0].file_name().unwrap().to_string_lossy())) + }, + }; + + let cpus = if cli_args.single_thread { 1 } else { num_cpus::get() }; + + let format = ArchiveFormat::get_archive_format_from_file_path(output_path.as_path())?; + + if cli_args.password.is_some() + && !matches!( + format, + ArchiveFormat::Tar7z | ArchiveFormat::P7z | ArchiveFormat::Zip | ArchiveFormat::Rar + ) + { + return Err(anyhow!("`password` only supports 7Z, ZIP and RAR.")); + } + + if split.is_some() + && !matches!( + format, + ArchiveFormat::Tar7z | ArchiveFormat::P7z | ArchiveFormat::Zip | ArchiveFormat::Rar + ) + { + return Err(anyhow!("`split` only supports 7Z, ZIP and RAR.")); + } + + if recovery_record.is_some() && !matches!(format, ArchiveFormat::Rar) { + return Err(anyhow!("`recovery-record` only supports RAR.")); + } + + let output_path = match output_path.canonicalize() { + Ok(output_path) => { + if output_path.is_dir() { + return Err(anyhow!("{output_path:?} is a directory.")); + } + + fs::remove_file(output_path.as_path())?; + + output_path + }, + Err(error) if error.kind() == io::ErrorKind::NotFound => { + match output_path.absolutize()? { + Cow::Borrowed(_) => output_path, + Cow::Owned(path) => path, + } + }, + Err(error) => { + return Err(error.into()); + }, + }; + + let compression_level = if best_compression { + CompressionLevel::Best + } else if fastest_compression { + CompressionLevel::Fast + } else { + CompressionLevel::Default + }; + + let threads = cpus.to_string(); + let threads = threads.as_str(); + + match format { + ArchiveFormat::TarZ + | ArchiveFormat::TarGzip + | ArchiveFormat::TarBzip2 + | ArchiveFormat::TarLz + | ArchiveFormat::TarXz + | ArchiveFormat::TarLzma + | ArchiveFormat::Tar7z + | ArchiveFormat::TarZstd => { + let mut command1 = + command_args!(&cli_args.executable_paths.tar_path, "-c", "-f", "-"); + + for input_path in input_paths { + let input_path = input_path.absolutize()?; + let input_path_parent = input_path.parent().unwrap(); + let file_name = input_path.file_name().unwrap(); + + command1.arg("-C"); + command1.arg(input_path_parent); + command1.arg(file_name); + } + + match format { + ArchiveFormat::TarZ => { + let mut command2 = + command_args!(&cli_args.executable_paths.compress_path, "-c", "-"); + + command2.stdout(File::create(output_path.as_path())?); + + let output = command1 + .execute_multiple_output(&mut [&mut command2]) + .map_err(|err| { + try_delete_file(output_path.as_path()); + err + })?; + + match output.status.code() { + Some(code) => { + if code != 0 && code != 2 { + try_delete_file(output_path.as_path()); + } + + process::exit(code); + }, + None => { + try_delete_file(output_path.as_path()); + process::exit(1); + }, + } + }, + ArchiveFormat::TarGzip => { + if (cpus > 1 || compression_level == CompressionLevel::Best) + && command_args!(&cli_args.executable_paths.pigz_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command2 = command_args!( + &cli_args.executable_paths.pigz_path, + "-c", + "-p", + threads, + "-" + ); + + if cli_args.quiet { + command2.arg("-q"); + } + + match compression_level { + CompressionLevel::Best => { + command2.arg("-11"); + }, + CompressionLevel::Fast => { + command2.arg("-1"); + }, + CompressionLevel::Default => (), + } + + command2.stdout(File::create(output_path.as_path())?); + + let output = command1 + .execute_multiple_output(&mut [&mut command2]) + .map_err(|err| { + try_delete_file(output_path.as_path()); + err + })?; + + match output.status.code() { + Some(code) => { + if code != 0 { + try_delete_file(output_path.as_path()); + } + + process::exit(code); + }, + None => { + try_delete_file(output_path.as_path()); + process::exit(1); + }, + } + } + + let mut command2 = + command_args!(&cli_args.executable_paths.gzip_path, "-c", "-"); + + if cli_args.quiet { + command2.arg("-q"); + } + + match compression_level { + CompressionLevel::Best => { + command2.arg("-9"); + }, + CompressionLevel::Fast => { + command2.arg("-1"); + }, + CompressionLevel::Default => (), + } + + command2.stdout(File::create(output_path.as_path())?); + + let output = command1 + .execute_multiple_output(&mut [&mut command2]) + .map_err(|err| { + try_delete_file(output_path.as_path()); + err + })?; + + match output.status.code() { + Some(code) => { + if code != 0 { + try_delete_file(output_path.as_path()); + } + + process::exit(code); + }, + None => { + try_delete_file(output_path.as_path()); + process::exit(1); + }, + } + }, + ArchiveFormat::TarBzip2 => { + if cpus > 1 { + if command_args!(&cli_args.executable_paths.lbzip2_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command2 = command_args!( + &cli_args.executable_paths.lbzip2_path, + "-z", + "-c", + "-n", + threads, + "-" + ); + + if cli_args.quiet { + command2.arg("-q"); + } + + match compression_level { + CompressionLevel::Best => { + command2.arg("-9"); + }, + CompressionLevel::Fast => { + command2.arg("-1"); + }, + CompressionLevel::Default => (), + } + + command2.stdout(File::create(output_path.as_path())?); + + let output = command1 + .execute_multiple_output(&mut [&mut command2]) + .map_err(|err| { + try_delete_file(output_path.as_path()); + err + })?; + + match output.status.code() { + Some(code) => { + if code != 0 { + try_delete_file(output_path.as_path()); + } + + process::exit(code); + }, + None => { + try_delete_file(output_path.as_path()); + process::exit(1); + }, + } + } + + if command_args!(&cli_args.executable_paths.pbzip2_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command2 = command_args!( + &cli_args.executable_paths.pbzip2_path, + "-z", + "-c", + format!("-p{threads}"), + "-" + ); + + if cli_args.quiet { + command2.arg("-q"); + } + + match compression_level { + CompressionLevel::Best => { + command2.arg("-9"); + }, + CompressionLevel::Fast => { + command2.arg("-1"); + }, + CompressionLevel::Default => (), + } + + command2.stdout(File::create(output_path.as_path())?); + + let output = command1 + .execute_multiple_output(&mut [&mut command2]) + .map_err(|err| { + try_delete_file(output_path.as_path()); + err + })?; + + match output.status.code() { + Some(code) => { + if code != 0 { + try_delete_file(output_path.as_path()); + } + + process::exit(code); + }, + None => { + try_delete_file(output_path.as_path()); + process::exit(1); + }, + } + } + } + + let mut command2 = + command_args!(&cli_args.executable_paths.bzip2_path, "-z", "-c", "-"); + + if cli_args.quiet { + command2.arg("-q"); + } + + match compression_level { + CompressionLevel::Best => { + command2.arg("-9"); + }, + CompressionLevel::Fast => { + command2.arg("-1"); + }, + CompressionLevel::Default => (), + } + + command2.stdout(File::create(output_path.as_path())?); + + let output = command1 + .execute_multiple_output(&mut [&mut command2]) + .map_err(|err| { + try_delete_file(output_path.as_path()); + err + })?; + + match output.status.code() { + Some(code) => { + if code != 0 { + try_delete_file(output_path.as_path()); + } + + process::exit(code); + }, + None => { + try_delete_file(output_path.as_path()); + process::exit(1); + }, + } + }, + ArchiveFormat::TarLz => { + if cpus > 1 + && command_args!(&cli_args.executable_paths.plzip_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command2 = command_args!( + &cli_args.executable_paths.plzip_path, + "-F", + "-c", + "-n", + threads, + "-" + ); + + if cli_args.quiet { + command2.arg("-q"); + } + + match compression_level { + CompressionLevel::Best => { + command2.arg("-9"); + }, + CompressionLevel::Fast => { + command2.arg("-1"); + }, + CompressionLevel::Default => (), + } + + command2.stdout(File::create(output_path.as_path())?); + + let output = command1 + .execute_multiple_output(&mut [&mut command2]) + .map_err(|err| { + try_delete_file(output_path.as_path()); + err + })?; + + match output.status.code() { + Some(code) => { + if code != 0 { + try_delete_file(output_path.as_path()); + } + + process::exit(code); + }, + None => { + try_delete_file(output_path.as_path()); + process::exit(1); + }, + } + } + + let mut command2 = + command_args!(&cli_args.executable_paths.lzip_path, "-F", "-c", "-"); + + if cli_args.quiet { + command2.arg("-q"); + } + + match compression_level { + CompressionLevel::Best => { + command2.arg("-9"); + }, + CompressionLevel::Fast => { + command2.arg("-1"); + }, + CompressionLevel::Default => (), + } + + command2.stdout(File::create(output_path.as_path())?); + + let output = command1 + .execute_multiple_output(&mut [&mut command2]) + .map_err(|err| { + try_delete_file(output_path.as_path()); + err + })?; + + match output.status.code() { + Some(code) => { + if code != 0 { + try_delete_file(output_path.as_path()); + } + + process::exit(code); + }, + None => { + try_delete_file(output_path.as_path()); + process::exit(1); + }, + } + }, + ArchiveFormat::TarXz => { + if cpus > 1 + && command_args!(&cli_args.executable_paths.pxz_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command2 = command_args!( + &cli_args.executable_paths.pxz_path, + "-z", + "-c", + "-T", + threads, + "-" + ); + + if cli_args.quiet { + command2.arg("-q"); + } + + match compression_level { + CompressionLevel::Best => { + command2.args(["-9", "-e"]); + }, + CompressionLevel::Fast => { + command2.arg("-0"); + }, + CompressionLevel::Default => (), + } + + command2.stdout(File::create(output_path.as_path())?); + + let output = command1 + .execute_multiple_output(&mut [&mut command2]) + .map_err(|err| { + try_delete_file(output_path.as_path()); + err + })?; + + match output.status.code() { + Some(code) => { + if code != 0 { + try_delete_file(output_path.as_path()); + } + + process::exit(code); + }, + None => { + try_delete_file(output_path.as_path()); + process::exit(1); + }, + } + } + + let mut command2 = + command_args!(&cli_args.executable_paths.xz_path, "-z", "-c", "-"); + + if cli_args.quiet { + command2.arg("-q"); + } + + match compression_level { + CompressionLevel::Best => { + command2.args(["-9", "-e"]); + }, + CompressionLevel::Fast => { + command2.arg("-0"); + }, + CompressionLevel::Default => (), + } + + command2.stdout(File::create(output_path.as_path())?); + + let output = command1 + .execute_multiple_output(&mut [&mut command2]) + .map_err(|err| { + try_delete_file(output_path.as_path()); + err + })?; + + match output.status.code() { + Some(code) => { + if code != 0 { + try_delete_file(output_path.as_path()); + } + + process::exit(code); + }, + None => { + try_delete_file(output_path.as_path()); + process::exit(1); + }, + } + }, + ArchiveFormat::TarLzma => { + if cpus > 1 + && command_args!(&cli_args.executable_paths.pxz_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command2 = command_args!( + &cli_args.executable_paths.pxz_path, + "-z", + "-c", + "-T", + threads, + "-F", + "lzma", + "-" + ); + + if cli_args.quiet { + command2.arg("-q"); + } + + match compression_level { + CompressionLevel::Best => { + command2.args(["-9", "-e"]); + }, + CompressionLevel::Fast => { + command2.arg("-0"); + }, + CompressionLevel::Default => (), + } + + command2.stdout(File::create(output_path.as_path())?); + + let output = command1 + .execute_multiple_output(&mut [&mut command2]) + .map_err(|err| { + try_delete_file(output_path.as_path()); + err + })?; + + match output.status.code() { + Some(code) => { + if code != 0 { + try_delete_file(output_path.as_path()); + } + + process::exit(code); + }, + None => { + try_delete_file(output_path.as_path()); + process::exit(1); + }, + } + } + + let mut command2 = + command_args!(&cli_args.executable_paths.lzma_path, "-z", "-c", "-"); + + if cli_args.quiet { + command2.arg("-q"); + } + + match compression_level { + CompressionLevel::Best => { + command2.args(["-9", "-e"]); + }, + CompressionLevel::Fast => { + command2.arg("-0"); + }, + CompressionLevel::Default => (), + } + + command2.stdout(File::create(output_path.as_path())?); + + let output = command1 + .execute_multiple_output(&mut [&mut command2]) + .map_err(|err| { + try_delete_file(output_path.as_path()); + err + })?; + + match output.status.code() { + Some(code) => { + if code != 0 { + try_delete_file(output_path.as_path()); + } + + process::exit(code); + }, + None => { + try_delete_file(output_path.as_path()); + process::exit(1); + }, + } + }, + ArchiveFormat::Tar7z => { + let password = read_password(cli_args.password)?; + + let mut command2 = command_args!( + &cli_args.executable_paths.p7z_path, + "a", + "-t7z", + "-aoa", + format!("-mmt{threads}"), + "-si", + ); + + match compression_level { + CompressionLevel::Best => { + command2.args(["-m0=lzma2", "-mx", "-ms=on"]); + }, + CompressionLevel::Fast => { + command2.arg("-m0=copy"); + }, + CompressionLevel::Default => (), + } + + if !password.is_empty() { + command2.arg("-mhe=on"); + command2.arg(format!("-p{password}")); + } + + if let Some(d) = split { + let byte = Byte::from_str(d)?; + + if byte.get_bytes() < 65536 { + return Err(anyhow!("The split size is too small.")); + } else { + command2.arg(format!( + "-v{}k", + byte.get_adjusted_unit(ByteUnit::KiB).get_value().round() + as u32 + )); + } + } + + command2.arg(output_path.as_path()); + + if cli_args.quiet { + process::exit( + command1.execute_multiple(&mut [&mut command2])?.unwrap_or(1), + ); + } else { + let output = command1.execute_multiple_output(&mut [&mut command2])?; + + process::exit(output.status.code().unwrap_or(1)); + } + }, + ArchiveFormat::TarZstd => { + if cpus > 1 + && command_args!(&cli_args.executable_paths.pzstd_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command2 = command_args!( + &cli_args.executable_paths.pzstd_path, + "-p", + threads, + "-", + "-o", + output_path.as_path() + ); + + if cli_args.quiet { + command2.arg("-q"); + } + + match compression_level { + CompressionLevel::Best => { + command2.args(["--ultra", "-22"]); + }, + CompressionLevel::Fast => { + command2.arg("-1"); + }, + CompressionLevel::Default => (), + } + + let output = command1.execute_multiple_output(&mut [&mut command2])?; + + process::exit(output.status.code().unwrap_or(1)); + } + + let mut command2 = command_args!( + &cli_args.executable_paths.zstd_path, + "-", + "-o", + output_path.as_path() + ); + + if cli_args.quiet { + command2.arg("-q"); + } + + match compression_level { + CompressionLevel::Best => { + command2.args(["--ultra", "-22"]); + }, + CompressionLevel::Fast => { + command2.arg("-1"); + }, + CompressionLevel::Default => (), + } + + let output = command1.execute_multiple_output(&mut [&mut command2])?; + + process::exit(output.status.code().unwrap_or(1)); + }, + _ => unreachable!(), + } + }, + ArchiveFormat::Tar => { + let mut command = command_args!(&cli_args.executable_paths.tar_path, "-c"); + + if !cli_args.quiet { + command.arg("-v"); + } + + command.arg("-f"); + + command.arg(output_path.as_path()); + + for input_path in input_paths { + let input_path = input_path.absolutize()?; + let input_path_parent = input_path.parent().unwrap(); + let file_name = input_path.file_name().unwrap(); + + command.arg("-C"); + command.arg(input_path_parent); + command.arg(file_name); + } + + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + }, + ArchiveFormat::Z => { + if input_paths.len() > 1 || input_paths[0].is_dir() { + return Err(anyhow!( + "Obviously, you should use .tar.Z for filename extension to support \ + multiple files." + )); + } + + let input_path = &input_paths[0]; + + let mut command = + command_args!(&cli_args.executable_paths.compress_path, "-c", input_path); + + command.stdout(File::create(output_path.as_path())?); + + let output = command.execute_output().map_err(|err| { + try_delete_file(output_path.as_path()); + err + })?; + + match output.status.code() { + Some(code) => { + if code != 0 { + try_delete_file(output_path.as_path()); + } + + process::exit(code); + }, + None => { + try_delete_file(output_path.as_path()); + process::exit(1); + }, + } + }, + ArchiveFormat::Gzip => { + if input_paths.len() > 1 || input_paths[0].is_dir() { + return Err(anyhow!( + "Obviously, you should use .tar.gz for filename extension to support \ + multiple files." + )); + } + + let input_path = &input_paths[0]; + + if (cpus > 1 || compression_level == CompressionLevel::Best) + && command_args!(&cli_args.executable_paths.pigz_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command = command_args!( + &cli_args.executable_paths.pigz_path, + "-c", + "-p", + threads, + input_path + ); + + if cli_args.quiet { + command.arg("-q"); + } + + match compression_level { + CompressionLevel::Best => { + command.arg("-9"); + }, + CompressionLevel::Fast => { + command.arg("-1"); + }, + CompressionLevel::Default => (), + } + + command.stdout(File::create(output_path.as_path())?); + + let output = command.execute_output().map_err(|err| { + try_delete_file(output_path.as_path()); + err + })?; + + match output.status.code() { + Some(code) => { + if code != 0 { + try_delete_file(output_path.as_path()); + } + + process::exit(code); + }, + None => { + try_delete_file(output_path.as_path()); + process::exit(1); + }, + } + } + + let mut command = + command_args!(&cli_args.executable_paths.gzip_path, "-c", input_path); + + if cli_args.quiet { + command.arg("-q"); + } + + match compression_level { + CompressionLevel::Best => { + command.arg("-9"); + }, + CompressionLevel::Fast => { + command.arg("-1"); + }, + CompressionLevel::Default => (), + } + + command.stdout(File::create(output_path.as_path())?); + + let output = command.execute_output().map_err(|err| { + try_delete_file(output_path.as_path()); + err + })?; + + match output.status.code() { + Some(code) => { + if code != 0 { + try_delete_file(output_path.as_path()); + } + + process::exit(code); + }, + None => { + try_delete_file(output_path.as_path()); + process::exit(1); + }, + } + }, + ArchiveFormat::Bzip2 => { + if input_paths.len() > 1 || input_paths[0].is_dir() { + return Err(anyhow!( + "Obviously, you should use .tar.bz2 for filename extension to support \ + multiple files." + )); + } + + let input_path = &input_paths[0]; + + if cpus > 1 { + if command_args!(&cli_args.executable_paths.lbzip2_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command = command_args!( + &cli_args.executable_paths.lbzip2_path, + "-z", + "-c", + "-n", + threads, + input_path + ); + + if cli_args.quiet { + command.arg("-q"); + } + + match compression_level { + CompressionLevel::Best => { + command.arg("-9"); + }, + CompressionLevel::Fast => { + command.arg("-1"); + }, + CompressionLevel::Default => (), + } + + command.stdout(File::create(output_path.as_path())?); + + let output = command.execute_output().map_err(|err| { + try_delete_file(output_path.as_path()); + err + })?; + + match output.status.code() { + Some(code) => { + if code != 0 { + try_delete_file(output_path.as_path()); + } + + process::exit(code); + }, + None => { + try_delete_file(output_path.as_path()); + process::exit(1); + }, + } + } + + if command_args!(&cli_args.executable_paths.pbzip2_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command = command_args!( + &cli_args.executable_paths.pbzip2_path, + "-z", + "-c", + format!("-p{threads}"), + input_path + ); + + if cli_args.quiet { + command.arg("-q"); + } + + match compression_level { + CompressionLevel::Best => { + command.arg("-9"); + }, + CompressionLevel::Fast => { + command.arg("-1"); + }, + CompressionLevel::Default => (), + } + + command.stdout(File::create(output_path.as_path())?); + + let output = command.execute_output().map_err(|err| { + try_delete_file(output_path.as_path()); + err + })?; + + match output.status.code() { + Some(code) => { + if code != 0 { + try_delete_file(output_path.as_path()); + } + + process::exit(code); + }, + None => { + try_delete_file(output_path.as_path()); + process::exit(1); + }, + } + } + } + + let mut command = + command_args!(&cli_args.executable_paths.bzip2_path, "-z", "-c", input_path); + + if cli_args.quiet { + command.arg("-q"); + } + + match compression_level { + CompressionLevel::Best => { + command.arg("-9"); + }, + CompressionLevel::Fast => { + command.arg("-1"); + }, + CompressionLevel::Default => (), + } + + command.stdout(File::create(output_path.as_path())?); + + let output = command.execute_output().map_err(|err| { + try_delete_file(output_path.as_path()); + err + })?; + + match output.status.code() { + Some(code) => { + if code != 0 { + try_delete_file(output_path.as_path()); + } + + process::exit(code); + }, + None => { + try_delete_file(output_path.as_path()); + process::exit(1); + }, + } + }, + ArchiveFormat::Lz => { + if input_paths.len() > 1 || input_paths[0].is_dir() { + return Err(anyhow!( + "Obviously, you should use .tar.lz for filename extension to support \ + multiple files." + )); + } + + let input_path = &input_paths[0]; + + if cpus > 1 + && command_args!(&cli_args.executable_paths.plzip_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command = command_args!( + &cli_args.executable_paths.plzip_path, + "-F", + "-c", + "-n", + threads, + input_path + ); + + if cli_args.quiet { + command.arg("-q"); + } + + match compression_level { + CompressionLevel::Best => { + command.arg("-9"); + }, + CompressionLevel::Fast => { + command.arg("-1"); + }, + CompressionLevel::Default => (), + } + + command.stdout(File::create(output_path.as_path())?); + + let output = command.execute_output().map_err(|err| { + try_delete_file(output_path.as_path()); + err + })?; + + match output.status.code() { + Some(code) => { + if code != 0 { + try_delete_file(output_path.as_path()); + } + + process::exit(code); + }, + None => { + try_delete_file(output_path.as_path()); + process::exit(1); + }, + } + } + + let mut command = + command_args!(&cli_args.executable_paths.lzip_path, "-F", "-c", input_path); + + if cli_args.quiet { + command.arg("-q"); + } + + match compression_level { + CompressionLevel::Best => { + command.arg("-9"); + }, + CompressionLevel::Fast => { + command.arg("-1"); + }, + CompressionLevel::Default => (), + } + + command.stdout(File::create(output_path.as_path())?); + + let output = command.execute_output().map_err(|err| { + try_delete_file(output_path.as_path()); + err + })?; + + match output.status.code() { + Some(code) => { + if code != 0 { + try_delete_file(output_path.as_path()); + } + + process::exit(code); + }, + None => { + try_delete_file(output_path.as_path()); + process::exit(1); + }, + } + }, + ArchiveFormat::Xz => { + if input_paths.len() > 1 || input_paths[0].is_dir() { + return Err(anyhow!( + "Obviously, you should use .tar.xz for filename extension to support \ + multiple files." + )); + } + + let input_path = &input_paths[0]; + + if cpus > 1 + && command_args!(&cli_args.executable_paths.pxz_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command = command_args!( + &cli_args.executable_paths.pxz_path, + "-z", + "-c", + "-T", + threads, + input_path + ); + + if cli_args.quiet { + command.arg("-q"); + } + + match compression_level { + CompressionLevel::Best => { + command.args(["-9", "-e"]); + }, + CompressionLevel::Fast => { + command.arg("-0"); + }, + CompressionLevel::Default => (), + } + + command.stdout(File::create(output_path.as_path())?); + + let output = command.execute_output().map_err(|err| { + try_delete_file(output_path.as_path()); + err + })?; + + match output.status.code() { + Some(code) => { + if code != 0 { + try_delete_file(output_path.as_path()); + } + + process::exit(code); + }, + None => { + try_delete_file(output_path.as_path()); + process::exit(1); + }, + } + } + + let mut command = + command_args!(&cli_args.executable_paths.xz_path, "-z", "-c", input_path); + + if cli_args.quiet { + command.arg("-q"); + } + + match compression_level { + CompressionLevel::Best => { + command.args(["-9", "-e"]); + }, + CompressionLevel::Fast => { + command.arg("-0"); + }, + CompressionLevel::Default => (), + } + + command.stdout(File::create(output_path.as_path())?); + + let output = command.execute_output().map_err(|err| { + try_delete_file(output_path.as_path()); + err + })?; + + match output.status.code() { + Some(code) => { + if code != 0 { + try_delete_file(output_path.as_path()); + } + + process::exit(code); + }, + None => { + try_delete_file(output_path.as_path()); + process::exit(1); + }, + } + }, + ArchiveFormat::Lzma => { + if input_paths.len() > 1 || input_paths[0].is_dir() { + return Err(anyhow!( + "Obviously, you should use .tar.lzma for filename extension to support \ + multiple files." + )); + } + + let input_path = &input_paths[0]; + + if cpus > 1 + && command_args!(&cli_args.executable_paths.pxz_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command = command_args!( + &cli_args.executable_paths.pxz_path, + "-z", + "-c", + "-T", + threads, + "-F", + "lzma", + input_path + ); + + if cli_args.quiet { + command.arg("-q"); + } + + match compression_level { + CompressionLevel::Best => { + command.args(["-9", "-e"]); + }, + CompressionLevel::Fast => { + command.arg("-0"); + }, + CompressionLevel::Default => (), + } + + command.stdout(File::create(output_path.as_path())?); + + let output = command.execute_output().map_err(|err| { + try_delete_file(output_path.as_path()); + err + })?; + + match output.status.code() { + Some(code) => { + if code != 0 { + try_delete_file(output_path.as_path()); + } + + process::exit(code); + }, + None => { + try_delete_file(output_path.as_path()); + process::exit(1); + }, + } + } + + let mut command = + command_args!(&cli_args.executable_paths.lzma_path, "-z", "-c", input_path); + + if cli_args.quiet { + command.arg("-q"); + } + + match compression_level { + CompressionLevel::Best => { + command.args(["-9", "-e"]); + }, + CompressionLevel::Fast => { + command.arg("-0"); + }, + CompressionLevel::Default => (), + } + + command.stdout(File::create(output_path.as_path())?); + + let output = command.execute_output().map_err(|err| { + try_delete_file(output_path.as_path()); + err + })?; + + match output.status.code() { + Some(code) => { + if code != 0 { + try_delete_file(output_path.as_path()); + } + + process::exit(code); + }, + None => { + try_delete_file(output_path.as_path()); + process::exit(1); + }, + } + }, + ArchiveFormat::P7z => { + let password = read_password(cli_args.password)?; + + let mut command = command_args!( + &cli_args.executable_paths.p7z_path, + "a", + "-t7z", + "-aoa", + format!("-mmt{threads}") + ); + + match compression_level { + CompressionLevel::Best => { + command.args(["-m0=lzma2", "-mx", "-ms=on"]); + }, + CompressionLevel::Fast => { + command.arg("-m0=copy"); + }, + CompressionLevel::Default => (), + } + + if !password.is_empty() { + command.arg("-mhe=on"); + command.arg(format!("-p{password}")); + } + + if let Some(d) = split { + let mut volume = String::from("-v"); + + let byte = Byte::from_str(d)?; + + if byte.get_bytes() < 65536 { + return Err(anyhow!("The split size is too small.")); + } else { + volume.push_str( + format!( + "{}k", + byte.get_adjusted_unit(ByteUnit::KiB).get_value().round() as u32 + ) + .as_str(), + ); + } + + command.arg(volume.as_str()); + } + + command.arg(output_path.as_path()); + + command.args(input_paths); + + if cli_args.quiet { + process::exit(command.execute()?.unwrap_or(1)); + } else { + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + } + }, + ArchiveFormat::Zip => { + let password = read_password(cli_args.password)?; + + let split = if let Some(d) = split { + let byte = Byte::from_str(d)?; + + if byte.get_bytes() < 65536 { + return Err(anyhow!("The split size is too small.")); + } + + Some(byte) + } else { + None + }; + + let output_tmp_path = if split.is_some() { + let new_filename = + format!("{}.tmp.zip", output_path.file_stem().unwrap().to_string_lossy()); + + let output_tmp_path = output_path.parent().unwrap().join(new_filename); + + if let Ok(metadata) = output_tmp_path.metadata() { + if metadata.is_dir() { + return Err(anyhow!("{output_path:?} is a directory.")); + } else { + fs::remove_file(output_tmp_path.as_path())?; + } + } + + Cow::from(output_tmp_path) + } else { + Cow::from(output_path.as_path()) + }; + + let mut command = command_args!( + &cli_args.executable_paths.p7z_path, + "a", + "-tzip", + "-aoa", + format!("-mmt{threads}") + ); + + match compression_level { + CompressionLevel::Best => { + command.arg("-mx"); + }, + CompressionLevel::Fast => { + command.arg("-mx=0"); + }, + CompressionLevel::Default => (), + } + + if !password.is_empty() { + command.arg(format!("-p{password}")); + } + + command.arg(output_tmp_path.as_ref()); + + command.args(input_paths); + + let exit_code = if cli_args.quiet { + command.execute()? + } else { + let output = command.execute_output()?; + + output.status.code() + }; + + if let Some(byte) = split { + match exit_code { + Some(code) => { + if code != 0 { + try_delete_file(output_path.as_path()); + process::exit(code); + } + }, + None => { + try_delete_file(output_path.as_path()); + process::exit(1); + }, + } + + let mut command = command_args!( + &cli_args.executable_paths.zip_path, + "-s", + format!( + "{}k", + byte.get_adjusted_unit(ByteUnit::KiB).get_value().round() as u32 + ) + ); + + match compression_level { + CompressionLevel::Best => { + command.arg("-9"); + }, + CompressionLevel::Fast => { + command.arg("-0"); + }, + CompressionLevel::Default => (), + } + + if !password.is_empty() { + command.arg("--password"); + command.arg(password); + } + + if cli_args.quiet { + command.arg("-q"); + } + + command.arg(output_tmp_path.as_ref()); + + command.arg("--out"); + + command.arg(output_path.as_path()); + + let output = command.execute_output().map_err(|err| { + try_delete_file(output_tmp_path.as_ref()); + err + })?; + + match output.status.code() { + Some(code) => { + try_delete_file(output_tmp_path.as_ref()); + process::exit(code); + }, + None => { + try_delete_file(output_tmp_path.as_ref()); + try_delete_file(output_path.as_path()); + process::exit(1); + }, + } + } + + process::exit(exit_code.unwrap_or(1)); + }, + ArchiveFormat::Rar => { + let password = read_password(cli_args.password)?; + + let mut command = command_args!( + &cli_args.executable_paths.rar_path, + "a", + "-ep1", + format!("-mt{threads}") + ); + + match compression_level { + CompressionLevel::Best => { + command.args(["-ma5", "-m5", "-s"]); + }, + CompressionLevel::Fast => { + command.arg("-m0"); + }, + CompressionLevel::Default => (), + } + + if !password.is_empty() { + command.arg(format!("-hp{password}")); + } + + if cli_args.quiet { + command.arg("-idq"); + } + + if let Some(d) = split { + let byte = Byte::from_str(d)?; + + if byte.get_bytes() < 65536 { + return Err(anyhow!("The split size is too small.")); + } else { + command.arg(format!( + "-v{}k", + byte.get_adjusted_unit(ByteUnit::KiB).get_value().round() as u32 + )); + } + } + + if let Some(rr) = recovery_record { + command.arg(format!("-rr{rr}",)); + } + + command.arg(output_path.as_path()); + + command.args(input_paths); + + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + }, + ArchiveFormat::Zstd => { + if input_paths.len() > 1 || input_paths[0].is_dir() { + return Err(anyhow!( + "Obviously, you should use .tar.zst for filename extension to support \ + multiple files." + )); + } + + let input_path = &input_paths[0]; + + if cpus > 1 + && command_args!(&cli_args.executable_paths.pzstd_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command = command_args!( + &cli_args.executable_paths.pzstd_path, + "-p", + threads, + input_path, + "-o", + output_path.as_path() + ); + + if cli_args.quiet { + command.arg("-q"); + } + + match compression_level { + CompressionLevel::Best => { + command.args(["--ultra", "-22"]); + }, + CompressionLevel::Fast => { + command.arg("-1"); + }, + CompressionLevel::Default => (), + } + + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + } + + let mut command = command_args!( + &cli_args.executable_paths.zstd_path, + input_path, + "-o", + output_path.as_path() + ); + + if cli_args.quiet { + command.arg("-q"); + } + + match compression_level { + CompressionLevel::Best => { + command.args(["--ultra", "-22"]); + }, + CompressionLevel::Fast => { + command.arg("-1"); + }, + CompressionLevel::Default => (), + } + + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + }, + } + } + + Ok(()) +} diff --git a/src/commands/decompression.rs b/src/commands/decompression.rs new file mode 100644 index 0000000..711326c --- /dev/null +++ b/src/commands/decompression.rs @@ -0,0 +1,978 @@ +use std::{fs, fs::File, io, process}; + +use anyhow::{anyhow, Context}; +use execute::{command_args, Execute}; +use path_absolutize::{Absolutize, CWD}; + +use super::read_password; +use crate::{ + archive_format::ArchiveFormat, + cli::{CLIArgs, CLICommands}, +}; + +pub fn handle_decompression(cli_args: CLIArgs) -> anyhow::Result<()> { + debug_assert!(matches!(cli_args.command, CLICommands::X { .. })); + + if let CLICommands::X { + input_path, + output_path, + output, + } = cli_args.command + { + let input_path = match input_path.canonicalize() { + Ok(input_path) => input_path, + Err(error) => { + return Err(error) + .with_context(|| format!("{:?}", input_path.absolutize().unwrap())); + }, + }; + + let output_path = if let Some(output_path) = output_path.as_deref() { + output_path + } else if let Some(output_path) = output.as_deref() { + output_path + } else { + CWD.as_path() + }; + + let cpus = if cli_args.single_thread { 1 } else { num_cpus::get() }; + + let format = ArchiveFormat::get_archive_format_from_file_path(input_path.as_path())?; + + if cli_args.password.is_some() + && !matches!( + format, + ArchiveFormat::Tar7z | ArchiveFormat::P7z | ArchiveFormat::Zip | ArchiveFormat::Rar + ) + { + return Err(anyhow!("`password` only supports 7Z, ZIP and RAR.")); + } + + let output_path = match output_path.canonicalize() { + Ok(output_path) => { + if !output_path.is_dir() { + return Err(anyhow!("{output_path:?} is not a directory.")); + } + + output_path + }, + Err(error) if error.kind() == io::ErrorKind::NotFound => { + fs::create_dir_all(output_path) + .with_context(|| format!("{:?}", output_path.absolutize().unwrap()))?; + + output_path + .canonicalize() + .with_context(|| format!("{:?}", output_path.absolutize().unwrap()))? + }, + Err(error) => { + return Err(error) + .with_context(|| format!("{:?}", output_path.absolutize().unwrap())); + }, + }; + + let threads = cpus.to_string(); + let threads = threads.as_str(); + + match format { + ArchiveFormat::TarZ | ArchiveFormat::TarGzip => { + if cpus > 1 + && command_args!(&cli_args.executable_paths.pigz_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command1 = command_args!( + &cli_args.executable_paths.pigz_path, + "-d", + "-c", + "-p", + threads, + input_path + ); + let mut command2 = command_args!( + &cli_args.executable_paths.tar_path, + "-x", + "-C", + output_path.as_path(), + "-f", + "-" + ); + + if !cli_args.quiet { + command2.arg("-v"); + } + + let output = command1.execute_multiple_output(&mut [&mut command2])?; + + process::exit(output.status.code().unwrap_or(1)); + } + + let mut command = command_args!( + &cli_args.executable_paths.tar_path, + "-z", + "-x", + "-C", + output_path.as_path(), + "-f", + input_path + ); + + if !cli_args.quiet { + command.arg("-v"); + } + + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + }, + ArchiveFormat::TarBzip2 => { + if cpus > 1 { + if command_args!(&cli_args.executable_paths.lbzip2_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command1 = command_args!( + &cli_args.executable_paths.lbzip2_path, + "-d", + "-c", + "-n", + threads, + input_path + ); + let mut command2 = command_args!( + &cli_args.executable_paths.tar_path, + "-x", + "-C", + output_path.as_path(), + "-f", + "-" + ); + + if !cli_args.quiet { + command2.arg("-v"); + } + + let output = command1.execute_multiple_output(&mut [&mut command2])?; + + process::exit(output.status.code().unwrap_or(1)); + } + + if command_args!(&cli_args.executable_paths.pbzip2_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command1 = command_args!( + &cli_args.executable_paths.pbzip2_path, + "-d", + "-c", + format!("-p{threads}"), + input_path + ); + let mut command2 = command_args!( + &cli_args.executable_paths.tar_path, + "-x", + "-C", + output_path.as_path(), + "-f", + "-" + ); + + if !cli_args.quiet { + command2.arg("-v"); + } + + let output = command1.execute_multiple_output(&mut [&mut command2])?; + + process::exit(output.status.code().unwrap_or(1)); + } + } + + let mut command = command_args!( + &cli_args.executable_paths.tar_path, + "-j", + "-x", + "-C", + output_path.as_path(), + "-f", + input_path + ); + + if !cli_args.quiet { + command.arg("-v"); + } + + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + }, + ArchiveFormat::TarLz => { + if cpus > 1 + && command_args!(&cli_args.executable_paths.plzip_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command1 = command_args!( + &cli_args.executable_paths.plzip_path, + "-d", + "-c", + "-n", + threads, + input_path + ); + let mut command2 = command_args!( + &cli_args.executable_paths.tar_path, + "-x", + "-C", + output_path.as_path(), + "-f", + "-" + ); + + if !cli_args.quiet { + command2.arg("-v"); + } + + let output = command1.execute_multiple_output(&mut [&mut command2])?; + + process::exit(output.status.code().unwrap_or(1)); + } + + if command_args!(&cli_args.executable_paths.lunzip_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command = command_args!( + &cli_args.executable_paths.tar_path, + "-I", + &cli_args.executable_paths.lunzip_path, + "-x", + "-C", + output_path.as_path(), + "-f", + input_path + ); + + if !cli_args.quiet { + command.arg("-v"); + } + + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + } + + let mut command = command_args!( + &cli_args.executable_paths.tar_path, + "-I", + &cli_args.executable_paths.lzip_path, + "-x", + "-C", + output_path.as_path(), + "-f", + input_path + ); + + if !cli_args.quiet { + command.arg("-v"); + } + + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + }, + ArchiveFormat::TarXz => { + if cpus > 1 + && command_args!(&cli_args.executable_paths.pxz_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command1 = command_args!( + &cli_args.executable_paths.pxz_path, + "-d", + "-c", + "-T", + threads, + input_path + ); + let mut command2 = command_args!( + &cli_args.executable_paths.tar_path, + "-x", + "-C", + output_path.as_path(), + "-f", + "-" + ); + + if !cli_args.quiet { + command2.arg("-v"); + } + + let output = command1.execute_multiple_output(&mut [&mut command2])?; + + process::exit(output.status.code().unwrap_or(1)); + } + + let mut command = command_args!( + &cli_args.executable_paths.tar_path, + "-J", + "-x", + "-C", + output_path.as_path(), + "-f", + input_path + ); + + if !cli_args.quiet { + command.arg("-v"); + } + + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + }, + ArchiveFormat::TarLzma => { + if cpus > 1 + && command_args!(&cli_args.executable_paths.pxz_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command1 = command_args!( + &cli_args.executable_paths.pxz_path, + "-d", + "-c", + "-T", + threads, + "-F", + "lzma", + input_path + ); + let mut command2 = command_args!( + &cli_args.executable_paths.tar_path, + "-x", + "-C", + output_path.as_path(), + "-f", + "-" + ); + + if !cli_args.quiet { + command2.arg("-v"); + } + + let output = command1.execute_multiple_output(&mut [&mut command2])?; + + process::exit(output.status.code().unwrap_or(1)); + } + + if command_args!(&cli_args.executable_paths.unlzma_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command = command_args!( + &cli_args.executable_paths.tar_path, + "-I", + &cli_args.executable_paths.unlzma_path, + "-x", + "-C", + output_path.as_path(), + "-f", + input_path + ); + + if !cli_args.quiet { + command.arg("-v"); + } + + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + } + + let mut command = command_args!( + &cli_args.executable_paths.tar_path, + "-I", + &cli_args.executable_paths.lzma_path, + "-x", + "-C", + output_path.as_path(), + "-f", + input_path + ); + + if !cli_args.quiet { + command.arg("-v"); + } + + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + }, + ArchiveFormat::Tar7z => { + let password = read_password(cli_args.password)?; + + let mut command1 = command_args!( + &cli_args.executable_paths.p7z_path, + "x", + "-so", + format!("-mmt{threads}") + ); + + command1.arg(format!("-p{password}")); + + command1.arg(input_path); + + let mut command2 = command_args!( + &cli_args.executable_paths.tar_path, + "-x", + "-C", + output_path.as_path(), + "-f", + "-" + ); + + if !cli_args.quiet { + command2.arg("-v"); + } + + let output = command1.execute_multiple_output(&mut [&mut command2])?; + + process::exit(output.status.code().unwrap_or(1)); + }, + ArchiveFormat::TarZstd => { + if cpus > 1 + && command_args!(&cli_args.executable_paths.pzstd_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command1 = command_args!( + &cli_args.executable_paths.pzstd_path, + "-d", + "-c", + "-p", + threads, + input_path + ); + let mut command2 = command_args!( + &cli_args.executable_paths.tar_path, + "-x", + "-C", + output_path.as_path(), + "-f", + "-" + ); + + if !cli_args.quiet { + command2.arg("-v"); + } + + let output = command1.execute_multiple_output(&mut [&mut command2])?; + + process::exit(output.status.code().unwrap_or(1)); + } + + if command_args!(&cli_args.executable_paths.unzstd_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command = command_args!( + &cli_args.executable_paths.tar_path, + "-I", + &cli_args.executable_paths.unzstd_path, + "-x", + "-C", + output_path.as_path(), + "-f", + input_path + ); + + if !cli_args.quiet { + command.arg("-v"); + } + + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + } + + let mut command = command_args!( + &cli_args.executable_paths.tar_path, + "-I", + &cli_args.executable_paths.zstd_path, + "-x", + "-C", + output_path.as_path(), + "-f", + input_path + ); + + if !cli_args.quiet { + command.arg("-v"); + } + + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + }, + ArchiveFormat::Tar => { + let mut command = command_args!( + &cli_args.executable_paths.tar_path, + "-x", + "-C", + output_path.as_path(), + "-f", + input_path + ); + + if !cli_args.quiet { + command.arg("-v"); + } + + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + }, + ArchiveFormat::Z | ArchiveFormat::Gzip => { + let file_path = output_path.join(input_path.file_stem().unwrap()); + + if file_path.is_dir() { + return Err(anyhow!("{file_path:?} is a directory.")); + } + + let file = + File::create(file_path.as_path()).with_context(|| format!("{file_path:?}"))?; + + if cpus > 1 + && command_args!(&cli_args.executable_paths.pigz_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command = command_args!( + &cli_args.executable_paths.pigz_path, + "-d", + "-c", + "-p", + threads, + input_path + ); + + command.stdout(file); + + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + } + + if command_args!(&cli_args.executable_paths.gnuzip_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command = + command_args!(&cli_args.executable_paths.gnuzip_path, "-c", input_path); + + command.stdout(file); + + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + } + + let mut command = + command_args!(&cli_args.executable_paths.gzip_path, "-d", "-c", input_path); + + command.stdout(file); + + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + }, + ArchiveFormat::Bzip2 => { + let file_path = output_path.join(input_path.file_stem().unwrap()); + + if file_path.is_dir() { + return Err(anyhow!("{file_path:?} is a directory.")); + } + + let file = + File::create(file_path.as_path()).with_context(|| format!("{file_path:?}"))?; + + if cpus > 1 { + if command_args!(&cli_args.executable_paths.lbzip2_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command = command_args!( + &cli_args.executable_paths.lbzip2_path, + "-d", + "-c", + "-n", + threads, + input_path + ); + + command.stdout(file); + + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + } + + if command_args!(&cli_args.executable_paths.pbzip2_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command = command_args!( + &cli_args.executable_paths.pbzip2_path, + "-d", + "-c", + format!("-p{threads}"), + input_path + ); + + command.stdout(file); + + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + } + } + + if command_args!(&cli_args.executable_paths.bunzip2_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command = + command_args!(&cli_args.executable_paths.bunzip2_path, "-c", input_path); + + command.stdout(file); + + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + } + + let mut command = + command_args!(&cli_args.executable_paths.bzip2_path, "-d", "-c", input_path); + + command.stdout(file); + + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + }, + ArchiveFormat::Lz => { + let file_path = output_path.join(input_path.file_stem().unwrap()); + + if file_path.is_dir() { + return Err(anyhow!("{file_path:?} is a directory.")); + } + + let file = + File::create(file_path.as_path()).with_context(|| format!("{file_path:?}"))?; + + if cpus > 1 + && command_args!(&cli_args.executable_paths.plzip_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command = command_args!( + &cli_args.executable_paths.plzip_path, + "-d", + "-c", + "-n", + threads, + input_path + ); + + command.stdout(file); + + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + } + + if command_args!(&cli_args.executable_paths.lunzip_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command = + command_args!(&cli_args.executable_paths.lunzip_path, "-c", input_path); + + command.stdout(file); + + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + } + + let mut command = + command_args!(&cli_args.executable_paths.lzip_path, "-d", "-c", input_path); + + command.stdout(file); + + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + }, + ArchiveFormat::Xz => { + let file_path = output_path.join(input_path.file_stem().unwrap()); + + if file_path.is_dir() { + return Err(anyhow!("{file_path:?} is a directory.")); + } + + let file = + File::create(file_path.as_path()).with_context(|| format!("{file_path:?}"))?; + + if cpus > 1 + && command_args!(&cli_args.executable_paths.pxz_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command = command_args!( + &cli_args.executable_paths.pxz_path, + "-d", + "-c", + "-T", + threads, + input_path + ); + + command.stdout(file); + + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + } + + if command_args!(&cli_args.executable_paths.unxz_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command = + command_args!(&cli_args.executable_paths.unxz_path, "-c", input_path); + + command.stdout(file); + + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + } + + let mut command = + command_args!(&cli_args.executable_paths.xz_path, "-d", "-c", input_path); + + command.stdout(file); + + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + }, + ArchiveFormat::Lzma => { + let file_path = output_path.join(input_path.file_stem().unwrap()); + + if file_path.is_dir() { + return Err(anyhow!("{file_path:?} is a directory.")); + } + + let file = + File::create(file_path.as_path()).with_context(|| format!("{file_path:?}"))?; + + if cpus > 1 + && command_args!(&cli_args.executable_paths.pxz_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command = command_args!( + &cli_args.executable_paths.pxz_path, + "-d", + "-c", + "-T", + threads, + "-F", + "lzma", + input_path + ); + + command.stdout(file); + + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + } + + if command_args!(&cli_args.executable_paths.unlzma_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command = + command_args!(&cli_args.executable_paths.unlzma_path, "-c", input_path); + + command.stdout(file); + + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + } + + let mut command = + command_args!(&cli_args.executable_paths.lzma_path, "-d", "-c", input_path); + + command.stdout(file); + + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + }, + ArchiveFormat::P7z => { + let password = read_password(cli_args.password)?; + + let mut command = command_args!( + &cli_args.executable_paths.p7z_path, + "x", + "-aoa", + format!("-mmt{threads}"), + format!("-o{}", output_path.to_string_lossy()) + ); + + command.arg(format!("-p{password}")); + + command.arg(input_path); + + if cli_args.quiet { + process::exit(command.execute()?.unwrap_or(1)); + } else { + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + } + }, + ArchiveFormat::Zip => { + let password = read_password(cli_args.password)?; + + let mut command = command_args!(&cli_args.executable_paths.unzip_path); + + command.arg("-P"); + command.arg(password); + + if cli_args.quiet { + command.arg("-qq"); + } + + command.args(["-O", "UTF-8", "-o"]); + command.arg(input_path); + command.arg("-d"); + command.arg(output_path.as_path()); + + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + }, + ArchiveFormat::Rar => { + let password = read_password(cli_args.password)?; + + if command_args!(&cli_args.executable_paths.unrar_path, "-?") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command = + command_args!(&cli_args.executable_paths.unrar_path, "x", "-o+"); + + command.arg(format!("-mt{threads}")); + + if password.is_empty() { + command.arg("-p-"); + } else { + command.arg(format!("-p{password}")); + } + + if cli_args.quiet { + command.arg("-idq"); + } + + command.arg(input_path); + command.arg(output_path.as_path()); + + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + } + + let mut command = command_args!(&cli_args.executable_paths.rar_path, "x", "-o+"); + + command.arg(format!("-mt{threads}")); + + if password.is_empty() { + command.arg("-p-"); + } else { + command.arg(format!("-p{password}")); + } + + if cli_args.quiet { + command.arg("-idq"); + } + + command.arg(input_path); + command.arg(output_path.as_path()); + + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + }, + ArchiveFormat::Zstd => { + let file_path = output_path.join(input_path.file_stem().unwrap()); + + if file_path.is_dir() { + return Err(anyhow!("{file_path:?} is a directory.")); + } + + let file = + File::create(file_path.as_path()).with_context(|| format!("{file_path:?}"))?; + + if cpus > 1 + && command_args!(&cli_args.executable_paths.pzstd_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command = command_args!( + &cli_args.executable_paths.pzstd_path, + "-d", + "-c", + "-p", + threads, + input_path + ); + + command.stdout(file); + + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + } + + if command_args!(&cli_args.executable_paths.unzstd_path, "-V") + .execute_check_exit_status_code(0) + .is_ok() + { + let mut command = + command_args!(&cli_args.executable_paths.unzstd_path, "-c", input_path); + + command.stdout(file); + + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + } + + let mut command = + command_args!(&cli_args.executable_paths.zstd_path, "-d", "-c", input_path); + + command.stdout(file); + + let output = command.execute_output()?; + + process::exit(output.status.code().unwrap_or(1)); + }, + } + } + + Ok(()) +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs new file mode 100644 index 0000000..a8984f3 --- /dev/null +++ b/src/commands/mod.rs @@ -0,0 +1,32 @@ +mod compression; +mod decompression; + +use std::{fs, io, io::Write, path::Path}; + +use anyhow::anyhow; +pub use compression::*; +pub use decompression::*; +use execute::generic_array::typenum::U32; +use scanner_rust::Scanner; + +#[inline] +fn try_delete_file>(file_path: P) { + if fs::remove_file(file_path).is_err() {} +} +fn read_password(password: Option) -> anyhow::Result { + match password { + Some(password) => { + if password.is_empty() { + print!("Password (visible): "); + io::stdout().flush()?; + + let mut sc: Scanner<_, U32> = Scanner::new2(io::stdin()); + + sc.next_line()?.ok_or_else(|| anyhow!("Stdin is closed.")) + } else { + Ok(password) + } + }, + None => Ok(String::new()), + } +} diff --git a/src/lib.rs b/src/lib.rs index 7665f43..54e3df8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,86 +1,5 @@ -//! # XCompress -//! XCompress is a free file archiver utility on Linux, providing multi-format archiving to and extracting from ZIP, Z, GZIP, BZIP2, LZ, XZ, LZMA, 7ZIP, TAR, RAR ans ZSTD. +/*! +# XCompress -use std::path::Path; - -#[derive(Debug)] -pub enum ArchiveFormat { - Z, - Zip, - Gzip, - Bzip2, - Lz, - Xz, - Lzma, - P7z, - Tar, - TarZ, - TarGzip, - TarBzip2, - TarLz, - TarXz, - TarLzma, - Tar7z, - TarZstd, - Rar, - Zstd, -} - -impl ArchiveFormat { - pub fn get_archive_format_from_file_path>( - file_path: P, - ) -> Result { - let file_path = file_path.as_ref(); - - if let Some(file_name) = file_path.file_name() { - if let Some(file_name) = file_name.to_str() { - let file_name = file_name.to_ascii_lowercase(); - - if file_name.ends_with("tar.z") { - return Ok(ArchiveFormat::TarZ); - } else if file_name.ends_with(".tar.gz") || file_name.ends_with(".tgz") { - return Ok(ArchiveFormat::TarGzip); - } else if file_name.ends_with(".tar.bz2") || file_name.ends_with(".tbz2") { - return Ok(ArchiveFormat::TarBzip2); - } else if file_name.ends_with(".tar.lz") { - return Ok(ArchiveFormat::TarLz); - } else if file_name.ends_with(".tar.xz") || file_name.ends_with(".txz") { - return Ok(ArchiveFormat::TarXz); - } else if file_name.ends_with(".tar.lzma") || file_name.ends_with(".tlz") { - return Ok(ArchiveFormat::TarLzma); - } else if file_name.ends_with(".tar.7z") - || file_name.ends_with(".tar.7z.001") - || file_name.ends_with(".t7z") - { - return Ok(ArchiveFormat::Tar7z); - } else if file_name.ends_with(".tar.zst") { - return Ok(ArchiveFormat::TarZstd); - } else if file_name.ends_with(".tar") { - return Ok(ArchiveFormat::Tar); - } else if file_name.ends_with(".z") { - return Ok(ArchiveFormat::Z); - } else if file_name.ends_with(".zip") { - return Ok(ArchiveFormat::Zip); - } else if file_name.ends_with(".gz") { - return Ok(ArchiveFormat::Gzip); - } else if file_name.ends_with(".bz2") { - return Ok(ArchiveFormat::Bzip2); - } else if file_name.ends_with(".lz") { - return Ok(ArchiveFormat::Lz); - } else if file_name.ends_with(".xz") { - return Ok(ArchiveFormat::Xz); - } else if file_name.ends_with(".lzma") { - return Ok(ArchiveFormat::Lzma); - } else if file_name.ends_with(".7z") || file_name.ends_with(".7z.001") { - return Ok(ArchiveFormat::P7z); - } else if file_name.ends_with(".rar") { - return Ok(ArchiveFormat::Rar); - } else if file_name.ends_with(".zst") { - return Ok(ArchiveFormat::Zstd); - } - } - } - - Err("Unknown archive format.") - } -} +XCompress is a free file archiver utility on Linux, providing multi-format archiving to and extracting from ZIP, Z, GZIP, BZIP2, LZ, XZ, LZMA, 7ZIP, TAR, RAR and ZSTD. +*/ diff --git a/src/main.rs b/src/main.rs index 4ce651d..a08442f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,2584 +1,24 @@ -use std::{ - borrow::Cow, - error::Error, - fs::{self, File}, - io::{self, Write}, - path::Path, - process, -}; +mod archive_format; +mod cli; +mod commands; -use byte_unit::{Byte, ByteUnit}; -use clap::{Arg, ArgMatches, Command}; -use concat_with::concat_line; -use execute::{command_args, Execute}; -use path_absolutize::{Absolutize, CWD}; -use scanner_rust::{generic_array::typenum::U32, Scanner}; -use terminal_size::terminal_size; -use xcompress::*; +use cli::*; -const APP_NAME: &str = "XCompress"; -const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); -const CARGO_PKG_AUTHORS: &str = env!("CARGO_PKG_AUTHORS"); +fn main() -> anyhow::Result<()> { + let args = get_args(); -const DEFAULT_COMPRESS_PATH: &str = "compress"; -const DEFAULT_ZIP_PATH: &str = "zip"; -const DEFAULT_UNZIP_PATH: &str = "unzip"; -const DEFAULT_GZIP_PATH: &str = "gzip"; -const DEFAULT_GUNZIP_PATH: &str = "gunzip"; -const DEFAULT_PIGZ_PATH: &str = "pigz"; -const DEFAULT_BZIP2_PATH: &str = "bzip2"; -const DEFAULT_BUNZIP2_PATH: &str = "bunzip2"; -const DEFAULT_LBZIP2_PATH: &str = "lbzip2"; -const DEFAULT_PBZIP2_PATH: &str = "pbzip2"; -const DEFAULT_LZIP_PATH: &str = "lzip"; -const DEFAULT_LUNZIP_PATH: &str = "lunzip"; -const DEFAULT_PLZIP_PATH: &str = "plzip"; -const DEFAULT_XZ_PATH: &str = "xz"; -const DEFAULT_UNXZ_PATH: &str = "unxz"; -const DEFAULT_PXZ_PATH: &str = "pxz"; -const DEFAULT_LZMA_PATH: &str = "lzma"; -const DEFAULT_UNLZMA_PATH: &str = "unlzma"; -const DEFAULT_7Z_PATH: &str = "7z"; -const DEFAULT_TAR_PATH: &str = "tar"; -const DEFAULT_RAR_PATH: &str = "rar"; -const DEFAULT_UNRAR_PATH: &str = "unrar"; -const DEFAULT_ZSTD_PATH: &str = "zstd"; -const DEFAULT_UNZSTD_PATH: &str = "unzstd"; -const DEFAULT_PZSTD_PATH: &str = "pzstd"; - -fn main() -> Result<(), Box> { - let matches = get_matches(); - - if let Some(sub_matches) = matches.subcommand_matches("x") { - let input_path = sub_matches.value_of("INPUT_PATH").unwrap(); - - let input_path = Path::new(input_path); - - if !input_path.exists() { - return Err( - format!("{} does not exist.", input_path.absolutize()?.to_string_lossy()).into() - ); - } - - let mut output_path = sub_matches.value_of("OUTPUT_PATH"); - let output_path2 = sub_matches.value_of("OUTPUT_PATH2"); - - if let Some(a) = output_path.as_ref() { - if let Some(b) = output_path2.as_ref() { - if a != b { - return Err("You input different output paths.".into()); - } - } - } else { - output_path = output_path2; - } - - let output_path = match output_path { - Some(output_path) => Path::new(output_path), - None => CWD.as_path(), - }; - - handle_extract(&matches, input_path, output_path) - } else if let Some(sub_matches) = matches.subcommand_matches("a") { - let input_paths_iter = sub_matches.values_of("INPUT_PATH").unwrap(); - - let mut input_paths = Vec::with_capacity(input_paths_iter.len()); - - for input_path in input_paths_iter { - let input_path = Path::new(input_path); - - if !input_path.exists() { - return Err(format!( - "{} does not exist.", - input_path.absolutize()?.to_string_lossy() - ) - .into()); - } - - input_paths.push(input_path); - } - - let output_path = sub_matches.value_of("OUTPUT_PATH"); - - let output_path = match output_path { - Some(output_path) => Cow::from(Path::new(output_path)), - None => Cow::from(CWD.join(format!( - "{}.rar", - input_paths[0].absolutize()?.file_name().unwrap().to_string_lossy() - ))), - }; - - let best_compression = sub_matches.is_present("BEST_COMPRESSION"); - - let split = sub_matches.value_of("SPLIT"); - - handle_archive(&matches, &input_paths, output_path, best_compression, split) - } else { - Err("Please input a subcommand. Use `help` to see how to use this program.".into()) - } -} - -fn handle_archive( - matches: &ArgMatches, - input_paths: &[&Path], - output_path: Cow, - best_compression: bool, - split: Option<&str>, -) -> Result<(), Box> { - let single_thread = matches.is_present("SINGLE_THREAD"); - let quiet = matches.is_present("QUIET"); - - let cpus = if single_thread { 1 } else { num_cpus::get() }; - - let format = ArchiveFormat::get_archive_format_from_file_path(output_path.as_ref())?; - - let output_path = output_path.absolutize()?; - - match output_path.metadata() { - Ok(metadata) => { - if metadata.is_dir() { - return Err(format!("{} is a directory.", output_path.to_string_lossy()).into()); - } - - fs::remove_file(output_path.as_ref())?; - }, - Err(_) => { - let output_path_parent = output_path.parent().unwrap(); - - fs::create_dir_all(output_path_parent)?; - }, - } - - let threads = cpus.to_string(); - let threads = threads.as_str(); - - match format { - ArchiveFormat::TarZ - | ArchiveFormat::TarGzip - | ArchiveFormat::TarBzip2 - | ArchiveFormat::TarLz - | ArchiveFormat::TarXz - | ArchiveFormat::TarLzma - | ArchiveFormat::Tar7z - | ArchiveFormat::TarZstd => { - let tar_path = matches.value_of("TAR_PATH").unwrap(); - - let mut command1 = command_args!(tar_path, "-c", "-f", "-"); - - for input_path in input_paths { - let input_path = input_path.absolutize()?; - let input_path_parent = input_path.parent().unwrap(); - let file_name = input_path.file_name().unwrap(); - - command1.arg("-C"); - command1.arg(input_path_parent); - command1.arg(file_name); - } - - match format { - ArchiveFormat::TarZ => { - let compress_path = matches.value_of("COMPRESS_PATH").unwrap(); - - let mut command2 = command_args!(compress_path, "-c", "-"); - - command2.stdout(File::create(output_path.as_ref())?); - - let output = - command1.execute_multiple_output(&mut [&mut command2]).map_err(|err| { - try_delete_file(output_path.as_ref()); - err - })?; - - match output.status.code() { - Some(code) => { - if code != 0 && code != 2 { - try_delete_file(output_path.as_ref()); - } - - process::exit(code); - }, - None => { - try_delete_file(output_path.as_ref()); - process::exit(1); - }, - } - }, - ArchiveFormat::TarGzip => { - if cpus > 1 || best_compression { - let pigz_path = matches.value_of("PIGZ_PATH").unwrap(); - - if command_args!(pigz_path, "-V").execute_check_exit_status_code(0).is_ok() - { - let mut command2 = command_args!(pigz_path, "-c", "-p", threads, "-"); - - if quiet { - command2.arg("-q"); - } - - if best_compression { - command2.arg("-11"); - } - - command2.stdout(File::create(output_path.as_ref())?); - - let output = command1 - .execute_multiple_output(&mut [&mut command2]) - .map_err(|err| { - try_delete_file(output_path.as_ref()); - err - })?; - - match output.status.code() { - Some(code) => { - if code != 0 { - try_delete_file(output_path.as_ref()); - } - - process::exit(code); - }, - None => { - try_delete_file(output_path.as_ref()); - process::exit(1); - }, - } - } - } - - let gzip_path = matches.value_of("GZIP_PATH").unwrap(); - - let mut command2 = command_args!(gzip_path, "-c", "-"); - - if quiet { - command2.arg("-q"); - } - - if best_compression { - command2.arg("-9"); - } - - command2.stdout(File::create(output_path.as_ref())?); - - let output = - command1.execute_multiple_output(&mut [&mut command2]).map_err(|err| { - try_delete_file(output_path.as_ref()); - err - })?; - - match output.status.code() { - Some(code) => { - if code != 0 { - try_delete_file(output_path.as_ref()); - } - - process::exit(code); - }, - None => { - try_delete_file(output_path.as_ref()); - process::exit(1); - }, - } - }, - ArchiveFormat::TarBzip2 => { - if cpus > 1 { - let lbzip2_path = matches.value_of("LBZIP2_PATH").unwrap(); - - if command_args!(lbzip2_path, "-V") - .execute_check_exit_status_code(0) - .is_ok() - { - let mut command2 = - command_args!(lbzip2_path, "-z", "-c", "-n", threads, "-"); - - if quiet { - command2.arg("-q"); - } - - if best_compression { - command2.arg("-9"); - } - - command2.stdout(File::create(output_path.as_ref())?); - - let output = command1 - .execute_multiple_output(&mut [&mut command2]) - .map_err(|err| { - try_delete_file(output_path.as_ref()); - err - })?; - - match output.status.code() { - Some(code) => { - if code != 0 { - try_delete_file(output_path.as_ref()); - } - - process::exit(code); - }, - None => { - try_delete_file(output_path.as_ref()); - process::exit(1); - }, - } - } - - let pbzip2_path = matches.value_of("PBZIP2_PATH").unwrap(); - - if command_args!(pbzip2_path, "-V") - .execute_check_exit_status_code(0) - .is_ok() - { - let mut command2 = command_args!( - pbzip2_path, - "-z", - "-c", - format!("-p{}", threads), - "-" - ); - - if quiet { - command2.arg("-q"); - } - - if best_compression { - command2.arg("-9"); - } - - command2.stdout(File::create(output_path.as_ref())?); - - let output = command1 - .execute_multiple_output(&mut [&mut command2]) - .map_err(|err| { - try_delete_file(output_path.as_ref()); - err - })?; - - match output.status.code() { - Some(code) => { - if code != 0 { - try_delete_file(output_path.as_ref()); - } - - process::exit(code); - }, - None => { - try_delete_file(output_path.as_ref()); - process::exit(1); - }, - } - } - } - - let bzip2_path = matches.value_of("BZIP2_PATH").unwrap(); - - let mut command2 = command_args!(bzip2_path, "-z", "-c", "-"); - - if quiet { - command2.arg("-q"); - } - - if best_compression { - command2.arg("-9"); - } - - command2.stdout(File::create(output_path.as_ref())?); - - let output = - command1.execute_multiple_output(&mut [&mut command2]).map_err(|err| { - try_delete_file(output_path.as_ref()); - err - })?; - - match output.status.code() { - Some(code) => { - if code != 0 { - try_delete_file(output_path.as_ref()); - } - - process::exit(code); - }, - None => { - try_delete_file(output_path.as_ref()); - process::exit(1); - }, - } - }, - ArchiveFormat::TarLz => { - if cpus > 1 { - let plzip_path = matches.value_of("PLZIP_PATH").unwrap(); - - if command_args!(plzip_path, "-V").execute_check_exit_status_code(0).is_ok() - { - let mut command2 = - command_args!(plzip_path, "-F", "-c", "-n", threads, "-"); - - if quiet { - command2.arg("-q"); - } - - if best_compression { - command2.arg("-9"); - } - - command2.stdout(File::create(output_path.as_ref())?); - - let output = command1 - .execute_multiple_output(&mut [&mut command2]) - .map_err(|err| { - try_delete_file(output_path.as_ref()); - err - })?; - - match output.status.code() { - Some(code) => { - if code != 0 { - try_delete_file(output_path.as_ref()); - } - - process::exit(code); - }, - None => { - try_delete_file(output_path.as_ref()); - process::exit(1); - }, - } - } - } - - let lzip_path = matches.value_of("LZIP_PATH").unwrap(); - - let mut command2 = command_args!(lzip_path, "-F", "-c", "-"); - - if quiet { - command2.arg("-q"); - } - - if best_compression { - command2.arg("-9"); - } - - command2.stdout(File::create(output_path.as_ref())?); - - let output = - command1.execute_multiple_output(&mut [&mut command2]).map_err(|err| { - try_delete_file(output_path.as_ref()); - err - })?; - - match output.status.code() { - Some(code) => { - if code != 0 { - try_delete_file(output_path.as_ref()); - } - - process::exit(code); - }, - None => { - try_delete_file(output_path.as_ref()); - process::exit(1); - }, - } - }, - ArchiveFormat::TarXz => { - if cpus > 1 { - let pxz_path = matches.value_of("PXZ_PATH").unwrap(); - - if command_args!(pxz_path, "-V").execute_check_exit_status_code(0).is_ok() { - let mut command2 = - command_args!(pxz_path, "-z", "-c", "-T", threads, "-"); - - if quiet { - command2.arg("-q"); - } - - if best_compression { - command2.args(["-9", "-e"]); - } - - command2.stdout(File::create(output_path.as_ref())?); - - let output = command1 - .execute_multiple_output(&mut [&mut command2]) - .map_err(|err| { - try_delete_file(output_path.as_ref()); - err - })?; - - match output.status.code() { - Some(code) => { - if code != 0 { - try_delete_file(output_path.as_ref()); - } - - process::exit(code); - }, - None => { - try_delete_file(output_path.as_ref()); - process::exit(1); - }, - } - } - } - - let xz_path = matches.value_of("XZ_PATH").unwrap(); - - let mut command2 = command_args!(xz_path, "-z", "-c", "-"); - - if quiet { - command2.arg("-q"); - } - - if best_compression { - command2.args(["-9", "-e"]); - } - - command2.stdout(File::create(output_path.as_ref())?); - - let output = - command1.execute_multiple_output(&mut [&mut command2]).map_err(|err| { - try_delete_file(output_path.as_ref()); - err - })?; - - match output.status.code() { - Some(code) => { - if code != 0 { - try_delete_file(output_path.as_ref()); - } - - process::exit(code); - }, - None => { - try_delete_file(output_path.as_ref()); - process::exit(1); - }, - } - }, - ArchiveFormat::TarLzma => { - if cpus > 1 { - let pxz_path = matches.value_of("PXZ_PATH").unwrap(); - - if command_args!(pxz_path, "-V").execute_check_exit_status_code(0).is_ok() { - let mut command2 = command_args!( - pxz_path, "-z", "-c", "-T", threads, "-F", "lzma", "-" - ); - - if quiet { - command2.arg("-q"); - } - - if best_compression { - command2.args(["-9", "-e"]); - } - - command2.stdout(File::create(output_path.as_ref())?); - - let output = command1 - .execute_multiple_output(&mut [&mut command2]) - .map_err(|err| { - try_delete_file(output_path.as_ref()); - err - })?; - - match output.status.code() { - Some(code) => { - if code != 0 { - try_delete_file(output_path.as_ref()); - } - - process::exit(code); - }, - None => { - try_delete_file(output_path.as_ref()); - process::exit(1); - }, - } - } - } - - let lzma_path = matches.value_of("LZMA_PATH").unwrap(); - - let mut command2 = command_args!(lzma_path, "-z", "-c", "-"); - - if quiet { - command2.arg("-q"); - } - - if best_compression { - command2.args(["-9", "-e"]); - } - - command2.stdout(File::create(output_path.as_ref())?); - - let output = - command1.execute_multiple_output(&mut [&mut command2]).map_err(|err| { - try_delete_file(output_path.as_ref()); - err - })?; - - match output.status.code() { - Some(code) => { - if code != 0 { - try_delete_file(output_path.as_ref()); - } - - process::exit(code); - }, - None => { - try_delete_file(output_path.as_ref()); - process::exit(1); - }, - } - }, - ArchiveFormat::Tar7z => { - let p7z_path = matches.value_of("7Z_PATH").unwrap(); - - let password = matches.value_of("PASSWORD"); - let password = read_password(password)?; - - let mut command2 = command_args!( - p7z_path, - "a", - "-t7z", - "-aoa", - format!("-mmt{}", threads), - "-si", - ); - - if best_compression { - command2.args(["-m0=lzma2", "-mx", "-ms=on"]); - } - - if !password.is_empty() { - command2.arg("-mhe=on"); - command2.arg(format!("-p{}", password)); - } - - if let Some(d) = split { - let byte = Byte::from_str(d)?; - - if byte.get_bytes() < 65536 { - return Err("The split size is too small.".into()); - } else { - command2.arg(format!( - "-v{}k", - byte.get_adjusted_unit(ByteUnit::KiB).get_value().round() as u32 - )); - } - } - - command2.arg(output_path.as_ref()); - - if quiet { - process::exit( - command1.execute_multiple(&mut [&mut command2])?.unwrap_or(1), - ); - } else { - let output = command1.execute_multiple_output(&mut [&mut command2])?; - - process::exit(output.status.code().unwrap_or(1)); - } - }, - ArchiveFormat::TarZstd => { - if cpus > 1 { - let pzstd_path = matches.value_of("PZSTD_PATH").unwrap(); - - if command_args!(pzstd_path, "-V").execute_check_exit_status_code(0).is_ok() - { - let mut command2 = command_args!( - pzstd_path, - "-p", - threads, - "-", - "-o", - output_path.as_ref() - ); - - if quiet { - command2.arg("-q"); - } - - if best_compression { - command2.args(["--ultra", "-22"]); - } - - let output = command1.execute_multiple_output(&mut [&mut command2])?; - - process::exit(output.status.code().unwrap_or(1)); - } - } - - let zstd_path = matches.value_of("ZSTD_PATH").unwrap(); - - let mut command2 = command_args!(zstd_path, "-", "-o", output_path.as_ref()); - - if quiet { - command2.arg("-q"); - } - - if best_compression { - command2.args(["--ultra", "-22"]); - } - - let output = command1.execute_multiple_output(&mut [&mut command2])?; - - process::exit(output.status.code().unwrap_or(1)); - }, - _ => unreachable!(), - } - }, - ArchiveFormat::Tar => { - let tar_path = matches.value_of("TAR_PATH").unwrap(); - - let mut command = command_args!(tar_path, "-c"); - - if !quiet { - command.arg("-v"); - } - - command.arg("-f"); - - command.arg(output_path.as_ref()); - - for input_path in input_paths { - let input_path = input_path.absolutize()?; - let input_path_parent = input_path.parent().unwrap(); - let file_name = input_path.file_name().unwrap(); - - command.arg("-C"); - command.arg(input_path_parent); - command.arg(file_name); - } - - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - }, - ArchiveFormat::Z => { - if input_paths.len() > 1 || input_paths[0].is_dir() { - return Err("Obviously, you should use .tar.Z for filename extension to support \ - multiple files." - .into()); - } - - let input_path = &input_paths[0]; - - let compress_path = matches.value_of("COMPRESS_PATH").unwrap(); - - let mut command = command_args!(compress_path, "-c", input_path); - - command.stdout(File::create(output_path.as_ref())?); - - let output = command.execute_output().map_err(|err| { - try_delete_file(output_path.as_ref()); - err - })?; - - match output.status.code() { - Some(code) => { - if code != 0 { - try_delete_file(output_path.as_ref()); - } - - process::exit(code); - }, - None => { - try_delete_file(output_path.as_ref()); - process::exit(1); - }, - } - }, - ArchiveFormat::Gzip => { - if input_paths.len() > 1 || input_paths[0].is_dir() { - return Err("Obviously, you should use .tar.gz for filename extension to support \ - multiple files." - .into()); - } - - let input_path = &input_paths[0]; - - if cpus > 1 || best_compression { - let pigz_path = matches.value_of("PIGZ_PATH").unwrap(); - - if command_args!(pigz_path, "-V").execute_check_exit_status_code(0).is_ok() { - let mut command = command_args!(pigz_path, "-c", "-p", threads, input_path); - - if quiet { - command.arg("-q"); - } - - if best_compression { - command.arg("-11"); - } - - command.stdout(File::create(output_path.as_ref())?); - - let output = command.execute_output().map_err(|err| { - try_delete_file(output_path.as_ref()); - err - })?; - - match output.status.code() { - Some(code) => { - if code != 0 { - try_delete_file(output_path.as_ref()); - } - - process::exit(code); - }, - None => { - try_delete_file(output_path.as_ref()); - process::exit(1); - }, - } - } - } - - let gzip_path = matches.value_of("GZIP_PATH").unwrap(); - - let mut command = command_args!(gzip_path, "-c", input_path); - - if quiet { - command.arg("-q"); - } - - if best_compression { - command.arg("-9"); - } - - command.stdout(File::create(output_path.as_ref())?); - - let output = command.execute_output().map_err(|err| { - try_delete_file(output_path.as_ref()); - err - })?; - - match output.status.code() { - Some(code) => { - if code != 0 { - try_delete_file(output_path.as_ref()); - } - - process::exit(code); - }, - None => { - try_delete_file(output_path.as_ref()); - process::exit(1); - }, - } - }, - ArchiveFormat::Bzip2 => { - if input_paths.len() > 1 || input_paths[0].is_dir() { - return Err("Obviously, you should use .tar.bz2 for filename extension to \ - support multiple files." - .into()); - } - - let input_path = &input_paths[0]; - - if cpus > 1 { - let lbzip2_path = matches.value_of("LBZIP2_PATH").unwrap(); - - if command_args!(lbzip2_path, "-V").execute_check_exit_status_code(0).is_ok() { - let mut command = - command_args!(lbzip2_path, "-z", "-c", "-n", threads, input_path); - - if quiet { - command.arg("-q"); - } - - if best_compression { - command.arg("-9"); - } - - command.stdout(File::create(output_path.as_ref())?); - - let output = command.execute_output().map_err(|err| { - try_delete_file(output_path.as_ref()); - err - })?; - - match output.status.code() { - Some(code) => { - if code != 0 { - try_delete_file(output_path.as_ref()); - } - - process::exit(code); - }, - None => { - try_delete_file(output_path.as_ref()); - process::exit(1); - }, - } - } - - let pbzip2_path = matches.value_of("PBZIP2_PATH").unwrap(); - - if command_args!(pbzip2_path, "-V").execute_check_exit_status_code(0).is_ok() { - let mut command = command_args!( - pbzip2_path, - "-z", - "-c", - format!("-p{}", threads), - input_path - ); - - if quiet { - command.arg("-q"); - } - - if best_compression { - command.arg("-9"); - } - - command.stdout(File::create(output_path.as_ref())?); - - let output = command.execute_output().map_err(|err| { - try_delete_file(output_path.as_ref()); - err - })?; - - match output.status.code() { - Some(code) => { - if code != 0 { - try_delete_file(output_path.as_ref()); - } - - process::exit(code); - }, - None => { - try_delete_file(output_path.as_ref()); - process::exit(1); - }, - } - } - } - - let bzip2_path = matches.value_of("BZIP2_PATH").unwrap(); - - let mut command = command_args!(bzip2_path, "-z", "-c", input_path); - - if quiet { - command.arg("-q"); - } - - if best_compression { - command.arg("-9"); - } - - command.stdout(File::create(output_path.as_ref())?); - - let output = command.execute_output().map_err(|err| { - try_delete_file(output_path.as_ref()); - err - })?; - - match output.status.code() { - Some(code) => { - if code != 0 { - try_delete_file(output_path.as_ref()); - } - - process::exit(code); - }, - None => { - try_delete_file(output_path.as_ref()); - process::exit(1); - }, - } - }, - ArchiveFormat::Lz => { - if input_paths.len() > 1 || input_paths[0].is_dir() { - return Err("Obviously, you should use .tar.lz for filename extension to support \ - multiple files." - .into()); - } - - let input_path = &input_paths[0]; - - if cpus > 1 { - let plzip_path = matches.value_of("PLZIP_PATH").unwrap(); - - if command_args!(plzip_path, "-V").execute_check_exit_status_code(0).is_ok() { - let mut command = - command_args!(plzip_path, "-F", "-c", "-n", threads, input_path); - - if quiet { - command.arg("-q"); - } - - if best_compression { - command.arg("-9"); - } - - command.stdout(File::create(output_path.as_ref())?); - - let output = command.execute_output().map_err(|err| { - try_delete_file(output_path.as_ref()); - err - })?; - - match output.status.code() { - Some(code) => { - if code != 0 { - try_delete_file(output_path.as_ref()); - } - - process::exit(code); - }, - None => { - try_delete_file(output_path.as_ref()); - process::exit(1); - }, - } - } - } - - let lzip_path = matches.value_of("LZIP_PATH").unwrap(); - - let mut command = command_args!(lzip_path, "-F", "-c", input_path); - - if quiet { - command.arg("-q"); - } - - if best_compression { - command.arg("-9"); - } - - command.stdout(File::create(output_path.as_ref())?); - - let output = command.execute_output().map_err(|err| { - try_delete_file(output_path.as_ref()); - err - })?; - - match output.status.code() { - Some(code) => { - if code != 0 { - try_delete_file(output_path.as_ref()); - } - - process::exit(code); - }, - None => { - try_delete_file(output_path.as_ref()); - process::exit(1); - }, - } - }, - ArchiveFormat::Xz => { - if input_paths.len() > 1 || input_paths[0].is_dir() { - return Err("Obviously, you should use .tar.xz for filename extension to support \ - multiple files." - .into()); - } - - let input_path = &input_paths[0]; - - if cpus > 1 { - let pxz_path = matches.value_of("PXZ_PATH").unwrap(); - - if command_args!(pxz_path, "-V").execute_check_exit_status_code(0).is_ok() { - let mut command = - command_args!(pxz_path, "-z", "-c", "-T", threads, input_path); - - if quiet { - command.arg("-q"); - } - - if best_compression { - command.args(["-9", "-e"]); - } - - command.stdout(File::create(output_path.as_ref())?); - - let output = command.execute_output().map_err(|err| { - try_delete_file(output_path.as_ref()); - err - })?; - - match output.status.code() { - Some(code) => { - if code != 0 { - try_delete_file(output_path.as_ref()); - } - - process::exit(code); - }, - None => { - try_delete_file(output_path.as_ref()); - process::exit(1); - }, - } - } - } - - let xz_path = matches.value_of("XZ_PATH").unwrap(); - - let mut command = command_args!(xz_path, "-z", "-c", input_path); - - if quiet { - command.arg("-q"); - } - - if best_compression { - command.args(["-9", "-e"]); - } - - command.stdout(File::create(output_path.as_ref())?); - - let output = command.execute_output().map_err(|err| { - try_delete_file(output_path.as_ref()); - err - })?; - - match output.status.code() { - Some(code) => { - if code != 0 { - try_delete_file(output_path.as_ref()); - } - - process::exit(code); - }, - None => { - try_delete_file(output_path.as_ref()); - process::exit(1); - }, - } - }, - ArchiveFormat::Lzma => { - if input_paths.len() > 1 || input_paths[0].is_dir() { - return Err("Obviously, you should use .tar.lzma for filename extension to \ - support multiple files." - .into()); - } - - let input_path = &input_paths[0]; - - if cpus > 1 { - let pxz_path = matches.value_of("PXZ_PATH").unwrap(); - - if command_args!(pxz_path, "-V").execute_check_exit_status_code(0).is_ok() { - let mut command = command_args!( - pxz_path, "-z", "-c", "-T", threads, "-F", "lzma", input_path - ); - - if quiet { - command.arg("-q"); - } - - if best_compression { - command.args(["-9", "-e"]); - } - - command.stdout(File::create(output_path.as_ref())?); - - let output = command.execute_output().map_err(|err| { - try_delete_file(output_path.as_ref()); - err - })?; - - match output.status.code() { - Some(code) => { - if code != 0 { - try_delete_file(output_path.as_ref()); - } - - process::exit(code); - }, - None => { - try_delete_file(output_path.as_ref()); - process::exit(1); - }, - } - } - } - - let lzma_path = matches.value_of("LZMA_PATH").unwrap(); - - let mut command = command_args!(lzma_path, "-z", "-c", input_path); - - if quiet { - command.arg("-q"); - } - - if best_compression { - command.args(["-9", "-e"]); - } - - command.stdout(File::create(output_path.as_ref())?); - - let output = command.execute_output().map_err(|err| { - try_delete_file(output_path.as_ref()); - err - })?; - - match output.status.code() { - Some(code) => { - if code != 0 { - try_delete_file(output_path.as_ref()); - } - - process::exit(code); - }, - None => { - try_delete_file(output_path.as_ref()); - process::exit(1); - }, - } - }, - ArchiveFormat::P7z => { - let p7z_path = matches.value_of("7Z_PATH").unwrap(); - - let password = matches.value_of("PASSWORD"); - let password = read_password(password)?; - - let mut command = - command_args!(p7z_path, "a", "-t7z", "-aoa", format!("-mmt{}", threads)); - - if best_compression { - command.args(["-m0=lzma2", "-mx", "-ms=on"]); - } - - if !password.is_empty() { - command.arg("-mhe=on"); - command.arg(format!("-p{}", password)); - } - - if let Some(d) = split { - let mut volume = String::from("-v"); - - let byte = Byte::from_str(d)?; - - if byte.get_bytes() < 65536 { - return Err("The split size is too small.".into()); - } else { - volume.push_str( - format!( - "{}k", - byte.get_adjusted_unit(ByteUnit::KiB).get_value().round() as u32 - ) - .as_str(), - ); - } - - command.arg(volume.as_str()); - } - - command.arg(output_path.as_ref()); - - command.args(input_paths); - - if quiet { - process::exit(command.execute()?.unwrap_or(1)); - } else { - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - } - }, - ArchiveFormat::Zip => { - let p7z_path = matches.value_of("7Z_PATH").unwrap(); - - let password = matches.value_of("PASSWORD"); - let password = read_password(password)?; - - let split = if let Some(d) = split { - let byte = Byte::from_str(d)?; - - if byte.get_bytes() < 65536 { - return Err("The split size is too small.".into()); - } - - Some(byte) - } else { - None - }; - - let output_tmp_path = if split.is_some() { - let new_filename = - format!("{}.tmp.zip", output_path.file_stem().unwrap().to_string_lossy()); - - let output_tmp_path = output_path.parent().unwrap().join(new_filename); - - if let Ok(metadata) = output_tmp_path.metadata() { - if metadata.is_dir() { - return Err(format!( - "{} is a directory.", - output_tmp_path.to_string_lossy() - ) - .into()); - } else { - fs::remove_file(output_tmp_path.as_path())?; - } - } - - Cow::from(output_tmp_path) - } else { - Cow::from(output_path.as_ref()) - }; - - let mut command = - command_args!(p7z_path, "a", "-tzip", "-aoa", format!("-mmt{}", threads)); - - if best_compression { - command.arg("-mx"); - } - - if !password.is_empty() { - command.arg(format!("-p{}", create_cli_string(&password))); - } - - command.arg(output_tmp_path.as_ref()); - - command.args(input_paths); - - let exit_code = if quiet { - command.execute()? - } else { - let output = command.execute_output()?; - - output.status.code() - }; - - if let Some(byte) = split { - match exit_code { - Some(code) => { - if code != 0 { - try_delete_file(output_path.as_ref()); - process::exit(code); - } - }, - None => { - try_delete_file(output_path.as_ref()); - process::exit(1); - }, - } - - let zip_path = matches.value_of("ZIP_PATH").unwrap(); - - let mut command = command_args!( - zip_path, - "-s", - format!( - "{}k", - byte.get_adjusted_unit(ByteUnit::KiB).get_value().round() as u32 - ) - ); - - if best_compression { - command.arg("-9"); - } - - if !password.is_empty() { - command.arg("--password"); - command.arg(password.as_ref()); - } - - if quiet { - command.arg("-q"); - } - - command.arg(output_tmp_path.as_ref()); - - command.arg("--out"); - - command.arg(output_path.as_ref()); - - let output = command.execute_output().map_err(|err| { - try_delete_file(output_tmp_path.as_ref()); - err - })?; - - match output.status.code() { - Some(code) => { - try_delete_file(output_tmp_path.as_ref()); - process::exit(code); - }, - None => { - try_delete_file(output_tmp_path.as_ref()); - try_delete_file(output_path.as_ref()); - process::exit(1); - }, - } - } - - process::exit(exit_code.unwrap_or(1)); - }, - ArchiveFormat::Rar => { - let rar_path = matches.value_of("RAR_PATH").unwrap(); - - let password = matches.value_of("PASSWORD"); - let password = read_password(password)?; - - let mut command = command_args!(rar_path, "a", "-ep1", format!("-mt{}", threads)); - - if best_compression { - command.args(["-ma5", "-m5", "-s"]); - } - - if !password.is_empty() { - command.arg(format!("-hp{}", create_cli_string(&password))); - } - - if quiet { - command.arg("-idq"); - } - - if let Some(d) = split { - let byte = Byte::from_str(d)?; - - if byte.get_bytes() < 65536 { - return Err("The split size is too small.".into()); - } else { - command.arg(format!( - "-v{}k", - byte.get_adjusted_unit(ByteUnit::KiB).get_value().round() as u32 - )); - } - } - - command.arg(output_path.as_ref()); - - command.args(input_paths); - - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - }, - ArchiveFormat::Zstd => { - if input_paths.len() > 1 || input_paths[0].is_dir() { - return Err("Obviously, you should use .tar.zst for filename extension to \ - support multiple files." - .into()); - } - - let input_path = &input_paths[0]; - - if cpus > 1 { - let pzstd_path = matches.value_of("PZSTD_PATH").unwrap(); - - if command_args!(pzstd_path, "-V").execute_check_exit_status_code(0).is_ok() { - let mut command = command_args!( - pzstd_path, - "-p", - threads, - input_path, - "-o", - output_path.as_ref() - ); - - if quiet { - command.arg("-q"); - } - - if best_compression { - command.args(["--ultra", "-22"]); - } - - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - } - } - - let zstd_path = matches.value_of("ZSTD_PATH").unwrap(); - - let mut command = command_args!(zstd_path, input_path, "-o", output_path.as_ref()); - - if quiet { - command.arg("-q"); - } - - if best_compression { - command.args(["--ultra", "-22"]); - } - - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - }, - } -} - -fn handle_extract( - matches: &ArgMatches, - input_path: &Path, - output_path: &Path, -) -> Result<(), Box> { - let single_thread = matches.is_present("SINGLE_THREAD"); - let quiet = matches.is_present("QUIET"); - - let cpus = if single_thread { 1 } else { num_cpus::get() }; - - let format = ArchiveFormat::get_archive_format_from_file_path(input_path)?; - - let output_path = output_path.absolutize()?; - - match output_path.metadata() { - Ok(metadata) => { - if !metadata.is_dir() { - return Err(format!("{} is not a directory.", output_path.to_string_lossy()).into()); - } - }, - Err(_) => { - fs::create_dir_all(output_path.as_ref())?; + match &args.command { + CLICommands::A { + .. + } => { + commands::handle_compression(args)?; }, - } - - let threads = cpus.to_string(); - let threads = threads.as_str(); - - match format { - ArchiveFormat::TarZ | ArchiveFormat::TarGzip => { - let tar_path = matches.value_of("TAR_PATH").unwrap(); - - if cpus > 1 { - let pigz_path = matches.value_of("PIGZ_PATH").unwrap(); - - if command_args!(pigz_path, "-V").execute_check_exit_status_code(0).is_ok() { - let mut command1 = - command_args!(pigz_path, "-d", "-c", "-p", threads, input_path); - let mut command2 = - command_args!(tar_path, "-x", "-C", output_path.as_ref(), "-f", "-"); - - if !quiet { - command2.arg("-v"); - } - - let output = command1.execute_multiple_output(&mut [&mut command2])?; - - process::exit(output.status.code().unwrap_or(1)); - } - } - - let mut command = - command_args!(tar_path, "-z", "-x", "-C", output_path.as_ref(), "-f", input_path); - - if !quiet { - command.arg("-v"); - } - - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - }, - ArchiveFormat::TarBzip2 => { - let tar_path = matches.value_of("TAR_PATH").unwrap(); - - if cpus > 1 { - let lbzip2_path = matches.value_of("LBZIP2_PATH").unwrap(); - - if command_args!(lbzip2_path, "-V").execute_check_exit_status_code(0).is_ok() { - let mut command1 = - command_args!(lbzip2_path, "-d", "-c", "-n", threads, input_path); - let mut command2 = - command_args!(tar_path, "-x", "-C", output_path.as_ref(), "-f", "-"); - - if !quiet { - command2.arg("-v"); - } - - let output = command1.execute_multiple_output(&mut [&mut command2])?; - - process::exit(output.status.code().unwrap_or(1)); - } - - let pbzip2_path = matches.value_of("PBZIP2_PATH").unwrap(); - - if command_args!(pbzip2_path, "-V").execute_check_exit_status_code(0).is_ok() { - let mut command1 = command_args!( - pbzip2_path, - "-d", - "-c", - format!("-p{}", threads), - input_path - ); - let mut command2 = - command_args!(tar_path, "-x", "-C", output_path.as_ref(), "-f", "-"); - - if !quiet { - command2.arg("-v"); - } - - let output = command1.execute_multiple_output(&mut [&mut command2])?; - - process::exit(output.status.code().unwrap_or(1)); - } - } - - let mut command = - command_args!(tar_path, "-j", "-x", "-C", output_path.as_ref(), "-f", input_path); - - if !quiet { - command.arg("-v"); - } - - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - }, - ArchiveFormat::TarLz => { - let tar_path = matches.value_of("TAR_PATH").unwrap(); - - if cpus > 1 { - let plzip_path = matches.value_of("PLZIP_PATH").unwrap(); - - if command_args!(plzip_path, "-V").execute_check_exit_status_code(0).is_ok() { - let mut command1 = - command_args!(plzip_path, "-d", "-c", "-n", threads, input_path); - let mut command2 = - command_args!(tar_path, "-x", "-C", output_path.as_ref(), "-f", "-"); - - if !quiet { - command2.arg("-v"); - } - - let output = command1.execute_multiple_output(&mut [&mut command2])?; - - process::exit(output.status.code().unwrap_or(1)); - } - } - - let lunzip_path = matches.value_of("LUNZIP_PATH").unwrap(); - - if command_args!(lunzip_path, "-V").execute_check_exit_status_code(0).is_ok() { - let mut command = command_args!( - tar_path, - "-I", - lunzip_path, - "-x", - "-C", - output_path.as_ref(), - "-f", - input_path - ); - - if !quiet { - command.arg("-v"); - } - - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - } - - let lzip_path = matches.value_of("LZIP_PATH").unwrap(); - - let mut command = command_args!( - tar_path, - "-I", - lzip_path, - "-x", - "-C", - output_path.as_ref(), - "-f", - input_path - ); - - if !quiet { - command.arg("-v"); - } - - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - }, - ArchiveFormat::TarXz => { - let tar_path = matches.value_of("TAR_PATH").unwrap(); - - if cpus > 1 { - let pxz_path = matches.value_of("PXZ_PATH").unwrap(); - - if command_args!(pxz_path, "-V").execute_check_exit_status_code(0).is_ok() { - let mut command1 = - command_args!(pxz_path, "-d", "-c", "-T", threads, input_path); - let mut command2 = - command_args!(tar_path, "-x", "-C", output_path.as_ref(), "-f", "-"); - - if !quiet { - command2.arg("-v"); - } - - let output = command1.execute_multiple_output(&mut [&mut command2])?; - - process::exit(output.status.code().unwrap_or(1)); - } - } - - let mut command = - command_args!(tar_path, "-J", "-x", "-C", output_path.as_ref(), "-f", input_path); - - if !quiet { - command.arg("-v"); - } - - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - }, - ArchiveFormat::TarLzma => { - let tar_path = matches.value_of("TAR_PATH").unwrap(); - - if cpus > 1 { - let pxz_path = matches.value_of("PXZ_PATH").unwrap(); - - if command_args!(pxz_path, "-V").execute_check_exit_status_code(0).is_ok() { - let mut command1 = command_args!( - pxz_path, "-d", "-c", "-T", threads, "-F", "lzma", input_path - ); - let mut command2 = - command_args!(tar_path, "-x", "-C", output_path.as_ref(), "-f", "-"); - - if !quiet { - command2.arg("-v"); - } - - let output = command1.execute_multiple_output(&mut [&mut command2])?; - - process::exit(output.status.code().unwrap_or(1)); - } - } - - let unlzma_path = matches.value_of("UNLZMA_PATH").unwrap(); - - if command_args!(unlzma_path, "-V").execute_check_exit_status_code(0).is_ok() { - let mut command = command_args!( - tar_path, - "-I", - unlzma_path, - "-x", - "-C", - output_path.as_ref(), - "-f", - input_path - ); - - if !quiet { - command.arg("-v"); - } - - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - } - - let lzma_path = matches.value_of("LZMA_PATH").unwrap(); - - let mut command = command_args!( - tar_path, - "-I", - lzma_path, - "-x", - "-C", - output_path.as_ref(), - "-f", - input_path - ); - - if !quiet { - command.arg("-v"); - } - - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - }, - ArchiveFormat::Tar7z => { - let p7z_path = matches.value_of("7Z_PATH").unwrap(); - let tar_path = matches.value_of("TAR_PATH").unwrap(); - - let password = matches.value_of("PASSWORD"); - let password = read_password(password)?; - - let mut command1 = command_args!(p7z_path, "x", "-so", format!("-mmt{}", threads)); - - command1.arg(format!("-p{}", create_cli_string(&password))); - - command1.arg(input_path); - - let mut command2 = command_args!(tar_path, "-x", "-C", output_path.as_ref(), "-f", "-"); - - if !quiet { - command2.arg("-v"); - } - - let output = command1.execute_multiple_output(&mut [&mut command2])?; - - process::exit(output.status.code().unwrap_or(1)); - }, - ArchiveFormat::TarZstd => { - let tar_path = matches.value_of("TAR_PATH").unwrap(); - - if cpus > 1 { - let pzstd_path = matches.value_of("PZSTD_PATH").unwrap(); - - if command_args!(pzstd_path, "-V").execute_check_exit_status_code(0).is_ok() { - let mut command1 = - command_args!(pzstd_path, "-d", "-c", "-p", threads, input_path); - let mut command2 = - command_args!(tar_path, "-x", "-C", output_path.as_ref(), "-f", "-"); - - if !quiet { - command2.arg("-v"); - } - - let output = command1.execute_multiple_output(&mut [&mut command2])?; - - process::exit(output.status.code().unwrap_or(1)); - } - } - - let unzstd_path = matches.value_of("UNZSTD_PATH").unwrap(); - - if command_args!(unzstd_path, "-V").execute_check_exit_status_code(0).is_ok() { - let mut command = command_args!( - tar_path, - "-I", - unzstd_path, - "-x", - "-C", - output_path.as_ref(), - "-f", - input_path - ); - - if !quiet { - command.arg("-v"); - } - - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - } - - let zstd_path = matches.value_of("ZSTD_PATH").unwrap(); - - let mut command = command_args!( - tar_path, - "-I", - zstd_path, - "-x", - "-C", - output_path.as_ref(), - "-f", - input_path - ); - - if !quiet { - command.arg("-v"); - } - - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - }, - ArchiveFormat::Tar => { - let tar_path = matches.value_of("TAR_PATH").unwrap(); - - let mut command = - command_args!(tar_path, "-x", "-C", output_path.as_ref(), "-f", input_path); - - if !quiet { - command.arg("-v"); - } - - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - }, - ArchiveFormat::Z | ArchiveFormat::Gzip => { - let file_path = output_path.join(Path::new(input_path).file_stem().unwrap()); - - if file_path.is_dir() { - return Err(format!("`{}` it is a directory.", file_path.to_string_lossy()).into()); - } - - let file = File::create(file_path)?; - - if cpus > 1 { - let pigz_path = matches.value_of("PIGZ_PATH").unwrap(); - - if command_args!(pigz_path, "-V").execute_check_exit_status_code(0).is_ok() { - let mut command = - command_args!(pigz_path, "-d", "-c", "-p", threads, input_path); - - command.stdout(file); - - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - } - } - - let gunzip_path = matches.value_of("GUNZIP_PATH").unwrap(); - - if command_args!(gunzip_path, "-V").execute_check_exit_status_code(0).is_ok() { - let mut command = command_args!(gunzip_path, "-c", input_path); - - command.stdout(file); - - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - } - - let gzip_path = matches.value_of("GZIP_PATH").unwrap(); - - let mut command = command_args!(gzip_path, "-d", "-c", input_path); - - command.stdout(file); - - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - }, - ArchiveFormat::Bzip2 => { - let file_path = output_path.join(Path::new(input_path).file_stem().unwrap()); - - if file_path.is_dir() { - return Err(format!("`{}` it is a directory.", file_path.to_string_lossy()).into()); - } - - let file = File::create(file_path)?; - - if cpus > 1 { - let lbzip2_path = matches.value_of("LBZIP2_PATH").unwrap(); - - if command_args!(lbzip2_path, "-V").execute_check_exit_status_code(0).is_ok() { - let mut command = - command_args!(lbzip2_path, "-d", "-c", "-n", threads, input_path); - - command.stdout(file); - - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - } - - let pbzip2_path = matches.value_of("PBZIP2_PATH").unwrap(); - - if command_args!(pbzip2_path, "-V").execute_check_exit_status_code(0).is_ok() { - let mut command = command_args!( - pbzip2_path, - "-d", - "-c", - format!("-p{}", threads), - input_path - ); - - command.stdout(file); - - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - } - } - - let bunzip2_path = matches.value_of("BUNZIP2_PATH").unwrap(); - - if command_args!(bunzip2_path, "-V").execute_check_exit_status_code(0).is_ok() { - let mut command = command_args!(bunzip2_path, "-c", input_path); - - command.stdout(file); - - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - } - - let bzip2_path = matches.value_of("BZIP2_PATH").unwrap(); - - let mut command = command_args!(bzip2_path, "-d", "-c", input_path); - - command.stdout(file); - - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - }, - ArchiveFormat::Lz => { - let file_path = output_path.join(Path::new(input_path).file_stem().unwrap()); - - if file_path.is_dir() { - return Err(format!("`{}` it is a directory.", file_path.to_string_lossy()).into()); - } - - let file = File::create(file_path)?; - - if cpus > 1 { - let plzip_path = matches.value_of("PLZIP_PATH").unwrap(); - - if command_args!(plzip_path, "-V").execute_check_exit_status_code(0).is_ok() { - let mut command = - command_args!(plzip_path, "-d", "-c", "-n", threads, input_path); - - command.stdout(file); - - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - } - } - - let lunzip_path = matches.value_of("LUNZIP_PATH").unwrap(); - - if command_args!(lunzip_path, "-V").execute_check_exit_status_code(0).is_ok() { - let mut command = command_args!(lunzip_path, "-c", input_path); - - command.stdout(file); - - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - } - - let lzip_path = matches.value_of("LZIP_PATH").unwrap(); - - let mut command = command_args!(lzip_path, "-d", "-c", input_path); - - command.stdout(file); - - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - }, - ArchiveFormat::Xz => { - let file_path = output_path.join(Path::new(input_path).file_stem().unwrap()); - - if file_path.is_dir() { - return Err(format!("`{}` it is a directory.", file_path.to_string_lossy()).into()); - } - - let file = File::create(file_path)?; - - if cpus > 1 { - let pxz_path = matches.value_of("PXZ_PATH").unwrap(); - - if command_args!(pxz_path, "-V").execute_check_exit_status_code(0).is_ok() { - let mut command = - command_args!(pxz_path, "-d", "-c", "-T", threads, input_path); - - command.stdout(file); - - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - } - } - - let unxz_path = matches.value_of("UNXZ_PATH").unwrap(); - - if command_args!(unxz_path, "-V").execute_check_exit_status_code(0).is_ok() { - let mut command = command_args!(unxz_path, "-c", input_path); - - command.stdout(file); - - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - } - - let xz_path = matches.value_of("XZ_PATH").unwrap(); - - let mut command = command_args!(xz_path, "-d", "-c", input_path); - - command.stdout(file); - - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - }, - ArchiveFormat::Lzma => { - let file_path = output_path.join(Path::new(input_path).file_stem().unwrap()); - - if file_path.is_dir() { - return Err(format!("`{}` it is a directory.", file_path.to_string_lossy()).into()); - } - - let file = File::create(file_path)?; - - if cpus > 1 { - let pxz_path = matches.value_of("PXZ_PATH").unwrap(); - - if command_args!(pxz_path, "-V").execute_check_exit_status_code(0).is_ok() { - let mut command = command_args!( - pxz_path, "-d", "-c", "-T", threads, "-F", "lzma", input_path - ); - - command.stdout(file); - - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - } - } - - let unlzma_path = matches.value_of("UNLZMA_PATH").unwrap(); - - if command_args!(unlzma_path, "-V").execute_check_exit_status_code(0).is_ok() { - let mut command = command_args!(unlzma_path, "-c", input_path); - - command.stdout(file); - - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - } - - let lzma_path = matches.value_of("LZMA_PATH").unwrap(); - - let mut command = command_args!(lzma_path, "-d", "-c", input_path); - - command.stdout(file); - - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - }, - ArchiveFormat::P7z => { - let p7z_path = matches.value_of("7Z_PATH").unwrap(); - - let password = matches.value_of("PASSWORD"); - let password = read_password(password)?; - - let mut command = command_args!( - p7z_path, - "x", - "-aoa", - format!("-mmt{}", threads), - format!("-o{}", output_path.to_string_lossy()) - ); - - command.arg(format!("-p{}", password)); - - command.arg(input_path); - - println!("{:?}", command); - - if quiet { - process::exit(command.execute()?.unwrap_or(1)); - } else { - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - } - }, - ArchiveFormat::Zip => { - let unzip_path = matches.value_of("UNZIP_PATH").unwrap(); - - let password = matches.value_of("PASSWORD"); - let password = read_password(password)?; - - let mut command = command_args!(unzip_path); - - command.arg("-P"); - command.arg(password.as_ref()); - - if quiet { - command.arg("-qq"); - } - - command.args(["-O", "UTF-8", "-o"]); - command.arg(input_path); - command.arg("-d"); - command.arg(output_path.as_ref()); - - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - }, - ArchiveFormat::Rar => { - let unrar_path = matches.value_of("UNRAR_PATH").unwrap(); - - let password = matches.value_of("PASSWORD"); - let password = read_password(password)?; - - if command_args!(unrar_path, "-?").execute_check_exit_status_code(0).is_ok() { - let mut command = command_args!(unrar_path, "x", "-o+"); - - command.arg(format!("-mt{}", threads)); - - if password.is_empty() { - command.arg("-p-"); - } else { - command.arg(format!("-p{}", create_cli_string(&password))); - } - - if quiet { - command.arg("-idq"); - } - - command.arg(input_path); - command.arg(output_path.as_ref()); - - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - } - - let rar_path = matches.value_of("RAR_PATH").unwrap(); - - let mut command = command_args!(rar_path, "x", "-o+"); - - command.arg(format!("-mt{}", threads)); - - if password.is_empty() { - command.arg("-p-"); - } else { - command.arg(format!("-p{}", create_cli_string(&password))); - } - - if quiet { - command.arg("-idq"); - } - - command.arg(input_path); - command.arg(output_path.as_ref()); - - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - }, - ArchiveFormat::Zstd => { - let file_path = output_path.join(Path::new(input_path).file_stem().unwrap()); - - if file_path.is_dir() { - return Err(format!("`{}` it is a directory.", file_path.to_string_lossy()).into()); - } - - let file = File::create(file_path)?; - - if cpus > 1 { - let pzstd_path = matches.value_of("PZSTD_PATH").unwrap(); - - if command_args!(pzstd_path, "-V").execute_check_exit_status_code(0).is_ok() { - let mut command = - command_args!(pzstd_path, "-d", "-c", "-p", threads, input_path); - - command.stdout(file); - - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - } - } - - let unzstd_path = matches.value_of("UNZSTD_PATH").unwrap(); - - if command_args!(unzstd_path, "-V").execute_check_exit_status_code(0).is_ok() { - let mut command = command_args!(unzstd_path, "-c", input_path); - - command.stdout(file); - - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - } - - let zstd_path = matches.value_of("ZSTD_PATH").unwrap(); - - let mut command = command_args!(zstd_path, "-d", "-c", input_path); - - command.stdout(file); - - let output = command.execute_output()?; - - process::exit(output.status.code().unwrap_or(1)); - }, - } -} - -#[inline] -fn try_delete_file>(file_path: P) { - if fs::remove_file(file_path).is_err() {} -} - -#[inline] -fn create_cli_string(string: &str) -> String { - string.replace(' ', "\\ ") -} - -fn read_password(password: Option<&str>) -> Result, Box> { - match password { - Some(password) => { - if password.is_empty() { - print!("Password (visible): "); - io::stdout().flush()?; - - let mut sc: Scanner<_, U32> = Scanner::new2(io::stdin()); - - sc.next_line()?.map(Cow::from).ok_or_else(|| "Stdin is closed.".into()) - } else { - Ok(Cow::from(password)) - } + CLICommands::X { + .. + } => { + commands::handle_decompression(args)?; }, - None => Ok(Cow::from("")), } -} -fn get_matches() -> ArgMatches { - Command::new(APP_NAME) - .term_width(terminal_size().map(|(width, _)| width.0 as usize).unwrap_or(0)) - .version(CARGO_PKG_VERSION) - .author(CARGO_PKG_AUTHORS) - .about(concat!("XCompress is a free file archiver utility on Linux, providing multi-format archiving to and extracting from ZIP, Z, GZIP, BZIP2, LZ, XZ, LZMA, 7ZIP, TAR and RAR.\n\nEXAMPLES:\n", concat_line!(prefix "xcompress ", - "a foo.wav # Archive foo.wav to foo.rar", - "a foo.wav /root/bar.txt # Archive foo.wav and /root/bar.txt to foo.rar", - "a -o /tmp/out.7z foo.wav # Archive foo.wav to /tmp/out.7z", - "a -b foo/bar # Archive foo/bar folder to bar.rar as small as possible", - "a -p password foo.wav # Archive foo.wav to foo.rar with a password", - "x foo.rar # Extract foo.rar into current working directory", - "x foo.tar.gz /tmp/out_folder # Extract foo.tar.gz into /tmp/out_folder", - "x -p password foo.rar # Extract foo.rar with a password into current working directory" - ))) - .arg(Arg::new("QUIET") - .global(true) - .long("quiet") - .short('q') - .help("Make programs not print anything on the screen.") - ) - .arg(Arg::new("SINGLE_THREAD") - .global(true) - .long("single-thread") - .short('s') - .help("Use only one thread.") - ) - .arg(Arg::new("PASSWORD") - .global(true) - .long("password") - .short('p') - .help("Set password for your archive file. (Only supports 7Z, ZIP and RAR.) Set an empty string to read a password from stdin.") - .takes_value(true) - .forbid_empty_values(false) - .display_order(0) - ) - .arg(Arg::new("COMPRESS_PATH") - .global(true) - .long("compress-path") - .help("Specify the path of your compress executable binary file.") - .takes_value(true) - .default_value(DEFAULT_COMPRESS_PATH) - ) - .arg(Arg::new("ZIP_PATH") - .global(true) - .long("zip-path") - .help("Specify the path of your zip executable binary file.") - .takes_value(true) - .default_value(DEFAULT_ZIP_PATH) - ) - .arg(Arg::new("UNZIP_PATH") - .global(true) - .long("unzip-path") - .help("Specify the path of your unzip executable binary file.") - .takes_value(true) - .default_value(DEFAULT_UNZIP_PATH) - ) - .arg(Arg::new("GZIP_PATH") - .global(true) - .long("gzip-path") - .help("Specify the path of your gzip executable binary file.") - .takes_value(true) - .default_value(DEFAULT_GZIP_PATH) - ) - .arg(Arg::new("GUNZIP_PATH") - .global(true) - .long("gunzip-path") - .help("Specify the path of your gunzip executable binary file.") - .takes_value(true) - .default_value(DEFAULT_GUNZIP_PATH) - ) - .arg(Arg::new("PIGZ_PATH") - .global(true) - .long("pigz-path") - .help("Specify the path of your pigz executable binary file.") - .takes_value(true) - .default_value(DEFAULT_PIGZ_PATH) - ) - .arg(Arg::new("BZIP2_PATH") - .global(true) - .long("bzip2-path") - .help("Specify the path of your bzip2 executable binary file.") - .takes_value(true) - .default_value(DEFAULT_BZIP2_PATH) - ) - .arg(Arg::new("BUNZIP2_PATH") - .global(true) - .long("bunzip2-path") - .help("Specify the path of your bunzip2 executable binary file.") - .takes_value(true) - .default_value(DEFAULT_BUNZIP2_PATH) - ) - .arg(Arg::new("LBZIP2_PATH") - .global(true) - .long("lbzip2-path") - .help("Specify the path of your lbzip2 executable binary file.") - .takes_value(true) - .default_value(DEFAULT_LBZIP2_PATH) - ) - .arg(Arg::new("PBZIP2_PATH") - .global(true) - .long("pbzip2-path") - .help("Specify the path of your pbzip2 executable binary file.") - .takes_value(true) - .default_value(DEFAULT_PBZIP2_PATH) - ) - .arg(Arg::new("LZIP_PATH") - .global(true) - .long("lzip-path") - .help("Specify the path of your lzip executable binary file.") - .takes_value(true) - .default_value(DEFAULT_LZIP_PATH) - ) - .arg(Arg::new("LUNZIP_PATH") - .global(true) - .long("lunzip-path") - .help("Specify the path of your lunzip executable binary file.") - .takes_value(true) - .default_value(DEFAULT_LUNZIP_PATH) - ) - .arg(Arg::new("PLZIP_PATH") - .global(true) - .long("plzip-path") - .help("Specify the path of your plzip executable binary file.") - .takes_value(true) - .default_value(DEFAULT_PLZIP_PATH) - ) - .arg(Arg::new("XZ_PATH") - .global(true) - .long("xz-path") - .help("Specify the path of your xz executable binary file.") - .takes_value(true) - .default_value(DEFAULT_XZ_PATH) - ) - .arg(Arg::new("UNXZ_PATH") - .global(true) - .long("unxz-path") - .help("Specify the path of your unxz executable binary file.") - .takes_value(true) - .default_value(DEFAULT_UNXZ_PATH) - ) - .arg(Arg::new("PXZ_PATH") - .global(true) - .long("pxz-path") - .help("Specify the path of your pxz executable binary file.") - .takes_value(true) - .default_value(DEFAULT_PXZ_PATH) - ) - .arg(Arg::new("LZMA_PATH") - .global(true) - .long("lzma-path") - .help("Specify the path of your lzma executable binary file.") - .takes_value(true) - .default_value(DEFAULT_LZMA_PATH) - ) - .arg(Arg::new("UNLZMA_PATH") - .global(true) - .long("unlzma-path") - .help("Specify the path of your unlzma executable binary file.") - .takes_value(true) - .default_value(DEFAULT_UNLZMA_PATH) - ) - .arg(Arg::new("7Z_PATH") - .global(true) - .long("7z-path") - .help("Specify the path of your 7z executable binary file.") - .takes_value(true) - .default_value(DEFAULT_7Z_PATH) - ) - .arg(Arg::new("TAR_PATH") - .global(true) - .long("tar-path") - .help("Specify the path of your tar executable binary file.") - .takes_value(true) - .default_value(DEFAULT_TAR_PATH) - ) - .arg(Arg::new("RAR_PATH") - .global(true) - .long("rar-path") - .help("Specify the path of your rar executable binary file.") - .takes_value(true) - .default_value(DEFAULT_RAR_PATH) - ) - .arg(Arg::new("UNRAR_PATH") - .global(true) - .long("unrar-path") - .help("Specify the path of your unrar executable binary file.") - .takes_value(true) - .default_value(DEFAULT_UNRAR_PATH) - ) - .arg(Arg::new("ZSTD_PATH") - .global(true) - .long("zstd-path") - .help("Specify the path of your zstd executable binary file.") - .takes_value(true) - .default_value(DEFAULT_ZSTD_PATH) - ) - .arg(Arg::new("UNZSTD_PATH") - .global(true) - .long("unzstd-path") - .help("Specify the path of your unzstd executable binary file.") - .takes_value(true) - .default_value(DEFAULT_UNZSTD_PATH) - ) - .arg(Arg::new("PZSTD_PATH") - .global(true) - .long("pzstd-path") - .help("Specify the path of your pzstd executable binary file.") - .takes_value(true) - .default_value(DEFAULT_PZSTD_PATH) - ) - .subcommand(Command::new("x") - .about("Extract files with full path.") - .arg(Arg::new("INPUT_PATH") - .required(true) - .help("Assign the source of your archived file. It should be a file path.") - ) - .arg(Arg::new("OUTPUT_PATH") - .required(false) - .help("Assign a destination of your extracted files. It should be a directory path.") - ) - .arg(Arg::new("OUTPUT_PATH2") - .long("output") - .short('o') - .help("Assign a destination of your extracted files. It should be a directory path.") - .takes_value(true) - .value_name("OUTPUT_PATH") - .display_order(1) - ) - .after_help("Enjoy it! https://magiclen.org") - ) - .subcommand(Command::new("a") - .about("Add files to archive. Excludes base directory from names. (e.g. add /path/to/folder, you can always get the \"folder\" in the root of the archive file, instead of /path/to/folder.)") - .arg(Arg::new("INPUT_PATH") - .required(true) - .help("Assign the source of your original files. It should be at least one file path.") - .multiple_values(true) - ) - .arg(Arg::new("OUTPUT_PATH") - .long("output") - .short('o') - .help("Assign a destination of your extracted files. It should be a file path. Specify the file extension name in order to determine which archive format you want to use. [default archive format: RAR]") - .takes_value(true) - .display_order(1) - ) - .arg(Arg::new("BEST_COMPRESSION") - .long("best-compression") - .short('b') - .help("If you are OK about the compression and depression time and want to save more disk space and network traffic, it will make the archive file as small as possible.") - .display_order(1) - ) - .arg(Arg::new("SPLIT") - .long("split") - .short('d') - .help("Split the archive file into volumes with a specified size. The unit of value is byte. You can also use KB, MB, KiB, MiB, etc, as a suffix. The minimum volume is 64 KiB. (Only supports 7Z, ZIP and RAR.)") - .takes_value(true) - .value_name("SIZE_OF_EACH_VOLUME") - .display_order(1) - ) - .after_help("Enjoy it! https://magiclen.org") - ) - .after_help("Enjoy it! https://magiclen.org") - .get_matches() + Ok(()) }