#[cfg(feature = "serde")]
use serde::Serialize;
#[cfg(feature = "serde")]
use serde::Deserialize;
use std::path::Path;
use std::io::BufRead as _;
use std::io::BufReader;
use std::io::ErrorKind;
use std::process::Command;
use std::ops::Not;
use std::fs::File;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
#[cfg_attr(feature = "serde", derive(Deserialize))]
pub enum DiffOp {
None,
Internal,
Subprocess {
command: &'static str,
args: Vec<&'static str>,
},
}
impl DiffOp {
#[must_use]
pub fn posix_diff() -> Self {
Self::Subprocess {
command: "diff",
args: vec![],
}
}
#[must_use]
pub fn posix_cmp() -> Self {
Self::Subprocess {
command: "cmp",
args: vec!["-s"],
}
}
pub fn diff(&self, a: &Path, b: &Path) -> Result<bool, std::io::Error> {
match self {
Self::None => Ok(a != b),
Self::Internal => {
let file_a = match File::options().read(true).open(a) {
Ok(f) => Some(f),
Err(e) if matches!(e.kind(), ErrorKind::NotFound) => None,
Err(e) => return Err(e),
};
let file_b = match File::options().read(true).open(b) {
Ok(f) => Some(f),
Err(e) if matches!(e.kind(), ErrorKind::NotFound) => None,
Err(e) => return Err(e),
};
match (file_a, file_b) {
(Some(a), Some(b)) => {
let meta_a = a.metadata().expect("get file metadata");
let meta_b = b.metadata().expect("get file metadata");
if meta_a.len() != meta_b.len()
|| meta_a.is_symlink()
|| meta_b.is_symlink()
|| meta_a.file_type() != meta_b.file_type()
{
Ok(false)
} else {
Self::internal_eq(&a, &b)
.map(bool::not)
}
},
(None, None) => Ok(true),
_ => Ok(false),
}
},
Self::Subprocess { command, args } => {
let status = Command::new(command)
.args(args)
.arg(a)
.arg(b)
.status()?;
match status.code() {
Some(0) => Ok(false),
Some(1) => Ok(true),
Some(_) => Err(std::io::Error::from(ErrorKind::Other)),
None => Err(std::io::Error::from(ErrorKind::Interrupted)),
}
},
}
}
fn internal_eq(a: &File, b: &File) -> Result<bool, std::io::Error> {
let mut buf_reader_a = BufReader::new(a);
let mut buf_reader_b = BufReader::new(b);
loop {
let buf_a = buf_reader_a.fill_buf()?;
let buf_b = buf_reader_b.fill_buf()?;
if buf_a.is_empty() && buf_b.is_empty() {
return Ok(true);
}
let read_len = if buf_a.len() <= buf_b.len() {
buf_a.len()
} else {
buf_b.len()
};
if buf_a[0..read_len] != buf_b[0..read_len] {
return Ok(false);
}
buf_reader_a.consume(read_len);
buf_reader_b.consume(read_len);
}
}
}