qbsdiff 1.3.1

Fast and memory saving bsdiff 4.x compatible delta compressor and patcher.
Documentation
#![forbid(unsafe_code)]
use qbsdiff::{Bsdiff, Compression, ParallelScheme};
use std::fs;
use std::io;
use std::io::prelude::*;
use std::process;
use std::str::FromStr;

#[macro_use]
extern crate clap;

fn main() {
    let matches = clap_app!(
        qbsdiff =>
        (version: "1.3.1")
        (about: "fast and memory saving bsdiff 4.x compatible delta compressor")
        (@arg NOPAR:
            -P
            "disable parallel searching")
        (@arg COMPRESS:
            -z +takes_value
            "bzip2 compression level (1-9)")
        (@arg BSIZE:
            -b +takes_value
            "buffer size")
        (@arg SMALL:
            -s +takes_value
            "skip small matches")
        (@arg SOURCE:
            +required
            "source file")
        (@arg TARGET:
            +required
            "target file")
        (@arg PATCH:
            +required
            "patch file"))
    .get_matches();

    let parallel = !matches.is_present("NOPAR");
    let compress_expr = matches.value_of("COMPRESS").unwrap_or("5");
    let bsize_expr = matches.value_of("BSIZE").unwrap_or("4096");
    let small_expr = matches.value_of("SMALL").unwrap_or("12");
    let source_name = matches.value_of("SOURCE").unwrap();
    let target_name = matches.value_of("TARGET").unwrap();
    let patch_name = matches.value_of("PATCH").unwrap();

    match BsdiffApp::new(
        parallel,
        compress_expr,
        bsize_expr,
        small_expr,
        source_name,
        target_name,
        patch_name,
    ) {
        Ok(app) => {
            if let Err(e) = app.execute() {
                eprintln!("error: {}", e);
                process::exit(1);
            }
        }
        Err(e) => {
            eprintln!("error: {}", e);
            process::exit(1);
        }
    }
}

struct BsdiffApp {
    source: Vec<u8>,
    target: Vec<u8>,
    patch: Box<dyn Write>,
    scheme: ParallelScheme,
    level: Compression,
    bsize: usize,
    small: usize,
}

impl BsdiffApp {
    pub fn new(
        parallel: bool,
        compress_expr: &str,
        bsize_expr: &str,
        small_expr: &str,
        source_name: &str,
        target_name: &str,
        patch_name: &str,
    ) -> io::Result<Self> {
        let scheme = if parallel {
            ParallelScheme::Auto
        } else {
            ParallelScheme::Never
        };

        let level = match parse_usize(compress_expr)? {
            1 | 2 | 3 => Compression::Fastest,
            4 | 5 | 6 => Compression::Default,
            7 | 8 | 9 => Compression::Best,
            _ => {
                return Err(io::Error::new(
                    io::ErrorKind::InvalidInput,
                    "compression level must be in range 1-9",
                ))
            }
        };

        let bsize = parse_usize(bsize_expr)?;
        let small = parse_usize(small_expr)?;

        if source_name == "-" && target_name == "-" {
            return Err(io::Error::new(
                io::ErrorKind::InvalidInput,
                "source and target could not be stdin at the same time",
            ));
        }

        let mut source;
        if source_name == "-" {
            source = Vec::new();
            io::stdin().read_to_end(&mut source)?;
        } else {
            source = fs::read(source_name)?;
        }
        source.shrink_to_fit();

        let mut target;
        if target_name == "-" {
            target = Vec::new();
            io::stdin().read_to_end(&mut target)?;
        } else {
            target = fs::read(target_name)?;
        }
        target.shrink_to_fit();

        let patch: Box<dyn Write>;
        if patch_name == "-" {
            patch = Box::new(io::stdout());
        } else {
            patch = Box::new(fs::File::create(patch_name)?);
        }

        Ok(BsdiffApp {
            source,
            target,
            patch,
            scheme,
            level,
            bsize,
            small,
        })
    }

    pub fn execute(self) -> io::Result<()> {
        Bsdiff::new(&self.source[..], &self.target[..])
            .parallel_scheme(self.scheme)
            .compression_level(self.level)
            .buffer_size(self.bsize)
            .small_match(self.small)
            .compare(self.patch)?;
        Ok(())
    }
}

fn parse_usize(expr: &str) -> io::Result<usize> {
    match usize::from_str(expr) {
        Ok(n) => Ok(n),
        Err(e) => Err(io::Error::new(io::ErrorKind::InvalidInput, e)),
    }
}