sunzip 0.0.1

7Z Compact/Extract Tool
Documentation
use clap::Parser;
use crc::{
    Algorithm, Crc, CRC_32_AIXM, CRC_32_AUTOSAR, CRC_32_BASE91_D, CRC_32_BZIP2, CRC_32_CD_ROM_EDC,
    CRC_32_CKSUM, CRC_32_ISCSI, CRC_32_ISO_HDLC, CRC_32_JAMCRC, CRC_32_MEF, CRC_32_MPEG_2,
    CRC_32_XFER, CRC_64_ECMA_182, CRC_64_GO_ISO, CRC_64_MS, CRC_64_REDIS, CRC_64_WE, CRC_64_XZ,
};
use iocore::Path;
use sevenz_rust::Password;
use sevenz_rust::SevenZArchiveEntry;
use sevenz_rust::SevenZReader;
use sunzip::errors::Result;

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = "sunzip command-line utility")]
pub struct Cli {
    #[arg()]
    path: Path,

    #[arg(short, long, env = "SUNZIP_PASSWORD")]
    password: Option<String>,

    #[arg(short, long)]
    force: bool,

    #[arg(short, long)]
    quiet: bool,
}
impl Cli {
    pub fn println(&self, string: impl std::fmt::Display) {
        if !self.quiet {
            println!("{}", string);
        }
    }
    pub fn eprintln(&self, string: impl std::fmt::Display) {
        if !self.quiet {
            eprintln!("{}", string);
        }
    }
}

pub fn crc_64_algorithms() -> Vec<String> {
    vec![
        format!("ECMA_182"),
        format!("GO_ISO"),
        format!("MS"),
        format!("REDIS"),
        format!("WE"),
        format!("XZ"),
    ]
}
pub fn crc_32_algorithms() -> Vec<String> {
    vec![
        format!("AIXM"),
        format!("AUTOSAR"),
        format!("BASE91_D"),
        format!("BZIP2"),
        format!("CD_ROM_EDC"),
        format!("CKSUM"),
        format!("ISCSI"),
        format!("ISO_HDLC"),
        format!("JAMCRC"),
        format!("MEF"),
        format!("MPEG_2"),
        format!("XFER"),
    ]
}

pub fn crc_64_algorithm(name: &str) -> &'static Algorithm<u64> {
    match name {
        "ECMA_182" => &CRC_64_ECMA_182,
        "GO_ISO" => &CRC_64_GO_ISO,
        "MS" => &CRC_64_MS,
        "REDIS" => &CRC_64_REDIS,
        "WE" => &CRC_64_WE,
        "XZ" => &CRC_64_XZ,
        unknown => panic!("unknown CRC64 algorithm {}", unknown),
    }
}
pub fn crc_32_algorithm(name: &str) -> &'static Algorithm<u32> {
    match name {
        "AIXM" => &CRC_32_AIXM,
        "AUTOSAR" => &CRC_32_AUTOSAR,
        "BASE91_D" => &CRC_32_BASE91_D,
        "BZIP2" => &CRC_32_BZIP2,
        "CD_ROM_EDC" => &CRC_32_CD_ROM_EDC,
        "CKSUM" => &CRC_32_CKSUM,
        "ISCSI" => &CRC_32_ISCSI,
        "ISO_HDLC" => &CRC_32_ISO_HDLC,
        "JAMCRC" => &CRC_32_JAMCRC,
        "MEF" => &CRC_32_MEF,
        "MPEG_2" => &CRC_32_MPEG_2,
        "XFER" => &CRC_32_XFER,
        unknown => panic!("unknown CRC32 algorithm {}", unknown),
    }
}

pub fn crc_64(data: &[u8]) -> Vec<(u64, String)> {
    let mut result = Vec::<(u64, String)>::new();
    for algorithm_name in crc_64_algorithms() {
        let crc = Crc::<u64>::new(crc_64_algorithm(&algorithm_name));
        let mut digest = crc.digest();
        digest.update(data);
        result.push((digest.finalize(), algorithm_name.to_string()));
    }
    result
}
pub fn crc_32(data: &[u8]) -> Vec<(u32, String)> {
    let mut result = Vec::<(u32, String)>::new();
    for algorithm_name in crc_32_algorithms() {
        let crc = Crc::<u32>::new(crc_32_algorithm(&algorithm_name));
        let mut digest = crc.digest();
        digest.update(data);
        result.push((digest.finalize(), algorithm_name.to_string()));
    }
    result
}
fn main() -> Result<()> {
    let args = Cli::parse();
    let path = args.path.try_canonicalize();
    let password = args
        .password
        .clone()
        .map(|password| Password::from(password.as_str()))
        .or_else(|| Some(Password::empty()))
        .unwrap();
    let mut reader = SevenZReader::open(&path.path(), password)?;
    let cwd = Path::cwd().join(path.without_extension().name());
    reader.for_each_entries(|entry: &SevenZArchiveEntry, reader| {
        let name = entry.name.to_string();
        args.println(format!("decompressing {}", &name));
        let mut bytes = Vec::<u8>::new();
        let bytecount = reader.read_to_end(&mut bytes)?;
        let path = cwd.join(&name);
        if path.is_file() && !args.force {
            let oldbytes = path.read_bytes().expect(&format!("read {}", &path));
            let mut crc64matches = false;
            let mut crc32matches = false;
            let mut matched_crc_algorithm_name: Option<String> = None;
            args.eprintln(format!(
                "{} already exists with CRC {} ({} compressed)",
                &path.relative_to_cwd(),
                hex::encode(&entry.crc.to_ne_bytes()),
                hex::encode(&entry.compressed_crc.to_ne_bytes())
            ));

            for (crc64, algorithm_name) in crc_64(&oldbytes) {
                args.eprintln(format!(
                    "calculating CRC 64 {}: {}",
                    &algorithm_name,
                    hex::encode(&crc64.to_ne_bytes())
                ));
                if crc64 == entry.crc {
                    crc64matches = true;
                    matched_crc_algorithm_name = Some(algorithm_name.to_string())
                }
            }
            for (crc32, algorithm_name) in crc_32(&oldbytes) {
                let crc32 = Into::<u64>::into(crc32);
                args.eprintln(format!(
                    "calculating CRC 32 {}: {}",
                    &algorithm_name,
                    hex::encode(&crc32.to_ne_bytes())
                ));
                if crc32 == entry.crc {
                    crc32matches = true;
                    matched_crc_algorithm_name = Some(algorithm_name.to_string())
                }
            }
            if crc64matches || crc32matches {
                args.eprintln(format!(
                    "{} matches successfully",
                    matched_crc_algorithm_name.expect("algorithm name")
                ));
            } else if !crc32matches {
                args.eprintln(format!("WARNING: no valid CRC32 match found!"));
            } else {
                args.eprintln(format!("WARNING: no valid CRC64 match found!"));
            }
        } else {
            path.write(&bytes).expect(&format!("write {}", &path));
            args.println(format!(
                "wrote {} bytes to {} (crc: {})",
                bytecount,
                &path.relative_to_cwd(),
                hex::encode(&entry.crc.to_ne_bytes())
            ));
        }
        Ok(true)
    })?;
    Ok(())
}