dd-lib 0.1.0

library functions for a clone of the unix coreutil dd
Documentation
use super::{super::results::elapsed, conv::ConvertSlice, read::Reader};
use opts::number::units;
use results::{Result, Success};
use std::{
    cmp::Ordering,
    io::{BufRead, Read, Write},
    time::SystemTime,
};

const BYTE_THRESHOLD_FOR_REPORT: usize = 5 * units::MB;
const BLOCK_THRESHOLD_FOR_REPORT: usize = 25;
const LINE_THRESHOLD_FOR_REPORT: usize = 20;
/// the standard byte-for-byte copy
pub fn standard<R, W, C, E>(mut r: Reader<R>, w: &mut W, c: C, err: &mut Option<E>) -> Result<Success>
where
    R: Read,
    W: Write,
    C: ConvertSlice<u8>,
    E: Write,
{
    let start = SystemTime::now();
    let mut converted: Vec<u8>;
    let mut bytes = 0;
    let mut prev_report = 0;
    while {
        converted = c.convert_copy(&mut r.fill_buf()?);
        converted.len() != 0
    } {
        w.write_all(&converted)?;
        r.consume(converted.len());
        bytes += converted.len();
        if let Some(err) = err {
            if bytes.saturating_sub(prev_report) > BYTE_THRESHOLD_FOR_REPORT {
                prev_report = bytes;
                writeln!(
                    err,
                    "dd in progress: elapsed: {}: wrote {} bytes",
                    elapsed(&start),
                    bytes
                );
            };
        };
    }

    Ok(Success::Bytes { bytes, start })
}
pub fn bytes<R, W, C, E>(mut r: R, w: &mut W, c: &C, err: &mut Option<E>, max_bytes: usize) -> Result<Success>
where
    R: BufRead,
    W: Write,
    C: ConvertSlice<u8>,
    E: Write,
{
    let start = SystemTime::now();
    let mut converted: Vec<u8>;
    let mut bytes: usize = 0;
    let mut read;
    let mut prev_report = 0;
    while {
        let slice = r.fill_buf()?;
        read = usize::min(max_bytes - bytes, slice.len());
        converted = c.convert_copy(&slice[..read]);

        read != 0
    } {
        w.write_all(&converted)?;
        r.consume(read);
        bytes += read;
        if let Some(err) = err {
            if bytes.saturating_sub(prev_report) > BYTE_THRESHOLD_FOR_REPORT {
                prev_report = bytes;
                writeln!(
                    err,
                    "dd in progress: elapsed: {}: wrote {} bytes",
                    elapsed(&start),
                    bytes
                );
            };
        };
    }

    Ok(Success::Bytes { bytes, start })
}

/// Treats the input as a sequence of newline or end-of-file terminated variable
/// length records independent of input and output block boundaries.  Any
/// trailing newline char- acter is discarded.  Each input record is converted
/// to a fixed length output record where the length is specified by the cbs
/// operand.  Input records shorter than the conversion record size are padded
/// with spaces.  Input records longer than the conver- sion record size are
/// truncated.  The number of truncated input records, if any, are reported to
/// the standard error output at the completion of the copy.
pub fn block<R, W, C, E>(r: R, w: &mut W, c: C, err: &mut Option<E>, block_size: usize) -> Result<Success>
where
    R: BufRead,
    W: Write,
    C: ConvertSlice<u8>,
    E: Write,
{
    let (mut lines, mut padded, mut truncated): (usize, usize, usize) = (0, 0, 0);
    let (mut buf, mut bytes): (Vec<u8>, std::io::Bytes<_>) = (Vec::with_capacity(block_size), r.bytes());
    let start = SystemTime::now();
    while {
        if let Some(b) = skip_trailing_while_match(&mut bytes, b'\n')? {
            buf.push(b)
        };
        buf = fill_buf_until_match(&mut bytes, buf, b'\n')?;
        buf.len() != 0
    } {
        lines += 1;
        match buf.len().cmp(&block_size) {
            Ordering::Greater => truncated += 1,
            Ordering::Less => padded += 1,
            Ordering::Equal => {},
        }

        c.convert_slice(&mut buf);
        buf.resize(block_size, b' ');

        w.write_all(&buf)?;

        if let Some(err) = err {
            if lines % LINE_THRESHOLD_FOR_REPORT == 0 {
                writeln!(
                    err,
                    "dd in progress: unblock: elapsed: {}, wrote {} lines",
                    elapsed(&start),
                    lines
                );
            }
        }
        buf.clear();
    }
    Ok(Success::Block {
        start,
        lines,
        truncated,
        padded,
        block_size,
    })
}

/// Treats the input as a sequence of fixed length records
/// independent of input and output block boundaries.  The
/// length of the input records is specified by the cbs op-
/// erand.  Any trailing space characters are discarded and
/// a newline character is appended.
pub fn unblock<R, W, C, E>(r: R, w: &mut W, c: C, err: &mut Option<E>, block_size: usize) -> Result<Success>
where
    R: BufRead,
    W: Write,
    C: ConvertSlice<u8>,
    E: Write,
{
    let start = SystemTime::now();
    let mut buf: Vec<u8> = Vec::with_capacity(block_size + 1); // account for newline
    let mut bytes = r.bytes();
    let mut blocks = 0;
    while {
        if let Some(b) = skip_trailing_while_match(&mut bytes, b' ')? {
            buf.push(b)
        };
        buf = fill_buf_to(&mut bytes, buf, block_size)?;
        buf.len() != 0
    } {
        buf.push(b'\n');

        c.convert_slice(&mut buf);
        w.write_all(&mut buf)?;

        blocks += 1;
        if let Some(err) = err {
            if blocks % BLOCK_THRESHOLD_FOR_REPORT == 0 {
                writeln!(
                    err,
                    "dd in progress: unblock: elapsed: {}, wrote {} fixed-length blocks",
                    elapsed(&start),
                    blocks
                );
            }
        }
        buf.clear();
    }
    Ok(Success::Unblock {
        blocks,
        block_size,
        start,
    })
}

/// skip items while they  match `unwanted_byte`, returning the first byte
/// (if any) that doesn't match
#[inline]
fn skip_trailing_while_match<I, T>(it: &mut I, unwanted: T) -> Result<Option<T>>
where
    I: Iterator<Item = ::std::io::Result<T>>,
    T: PartialEq,
{
    while let Some(res) = it.next() {
        let t = res?;
        if t != unwanted {
            return Ok(Some(t));
        }
    }
    return Ok(None);
}

/// fill the buffer until the matching `want` is found
#[inline]
fn fill_buf_until_match<I, T>(it: &mut I, mut buf: Vec<T>, want: T) -> Result<Vec<T>>
where
    I: Iterator<Item = ::std::io::Result<T>>,
    T: PartialEq,
{
    while let Some(res) = it.next() {
        let t = res?;
        if t == want {
            break;
        }
        buf.push(t)
    }
    Ok(buf)
}

#[inline]
fn fill_buf_to<I, T>(it: &mut I, mut buf: Vec<T>, cap: usize) -> Result<Vec<T>>
where
    I: Iterator<Item = ::std::io::Result<T>>,
{
    while buf.len() < cap {
        if let Some(res) = it.next() {
            buf.push(res?);
        } else {
            break;
        }
    }
    Ok(buf)
}