use opts::{CFlag, Error::ConflictingOption, OFlag, Opts};
use results::{Error, Result};
use std::{
fs::{File, OpenOptions},
io::{self, BufWriter, Seek, SeekFrom, Stdout, StdoutLock, Write},
path::Path,
};
pub struct Writer<'a, W: Write> {
w: Inner<'a, W>,
sparse: bool,
}
enum Inner<'a, W: Write> {
W(BufWriter<W>),
Stdout(BufWriter<StdoutLock<'a>>),
}
impl<'a, W: Write + Seek> Write for Writer<'a, W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match (&mut self.w, self.sparse && is_null(buf)) {
(Inner::W(w), true) => {
w.seek(SeekFrom::Current(buf.len() as i64))?;
Ok(buf.len())
},
(Inner::Stdout(_), true) => Ok(buf.len()),
(w, _) => w.write(buf),
}
}
fn flush(&mut self) -> std::io::Result<()> { self.w.flush() }
}
impl<'a, W: Write> Writer<'a, W> {
fn from_stdout(s: &'a Stdout, cap: usize, sparse: bool) -> Self {
let w = Inner::Stdout(BufWriter::with_capacity(cap, s.lock()));
Writer { w, sparse }
}
fn from_w(w: W, cap: usize, sparse: bool) -> Self {
let w = Inner::W(BufWriter::with_capacity(cap, w));
Writer { w, sparse }
}
}
impl<'a> Writer<'a, File> {
pub fn new(stdout: &'a Stdout, o: &Opts) -> Result<Self> {
match (o.output_file.clone(), o.output_seek) {
(Some(ref path), _) if o.oflag(OFlag::DIRECTORY) && !Path::is_dir(path.as_ref()) => Err(Error::from(
ConflictingOption("output flag 'directory' specified, but the path was not a directory"),
)),
(None, Some(_)) => Err(Error::from(ConflictingOption("cannot 'seek' on stdout"))),
(None, None) if o.oflag(OFlag::APPEND) => Err(Error::from(ConflictingOption("cannot 'append' on stdout"))),
(None, None) => Ok(Writer::from_stdout(stdout, o.input_block_size, o.cflag(CFlag::SPARSE))),
(Some(path), None) => {
let file = open(&path, o.oflag(OFlag::APPEND), !o.cflag(CFlag::NOTRUNC))?;
Ok(Writer::from_w(file, o.input_block_size, o.cflag(CFlag::SPARSE)))
},
(Some(path), Some(blocks)) => {
let mut file = open(&path, o.oflag(OFlag::APPEND), !o.cflag(CFlag::NOTRUNC))?;
file.seek(SeekFrom::Start(blocks as u64 * o.input_block_size as u64))?;
Ok(Writer::from_w(file, o.input_block_size, o.cflag(CFlag::SPARSE)))
},
}
}
#[allow(dead_code)]
pub unsafe fn get_file(self) -> File {
match self.w {
Inner::W(file) => file.into_inner().unwrap(),
_ => panic!("shouldn't have called this!"),
}
}
}
fn open(path: &str, append: bool, truncate: bool) -> Result<File> {
let file = OpenOptions::new()
.create(true)
.write(true)
.append(append)
.truncate(truncate)
.open(path)?;
Ok(file)
}
impl<'a, W: Write> Write for Inner<'a, W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match self {
Inner::W(w) => w.write(buf),
Inner::Stdout(stdout) => stdout.write(buf),
}
}
fn flush(&mut self) -> io::Result<()> {
match self {
Inner::W(w) => w.flush(),
Inner::Stdout(stdout) => stdout.flush(),
}
}
}
fn is_null(b: &[u8]) -> bool { b.iter().all(|b| *b == 0) }