mod os;
mod stat;
use std::collections::HashMap;
use std::fs::File;
use std::hash::{Hash, Hasher};
use std::io::{self, Read};
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use lazy_static::lazy_static;
const BUFSIZE: usize = 8 * 1024;
const FOLLOW_SYMLINKS_DEFAULT: bool = true;
const MAX_CACHE_SIZE: usize = 100;
lazy_static! {
static ref CACHE: Arc<Mutex<HashMap<(PathBuf, PathBuf, Signature, Signature), bool>>> = Arc::new(Mutex::new(HashMap::new()));
}
pub fn clear_cache() {
CACHE.lock().unwrap().clear();
}
pub fn cmp(f1: impl AsRef<Path>, f2: impl AsRef<Path>, shallow: bool) -> io::Result<bool> {
let s1 = sig(os::stat(f1.as_ref(), FOLLOW_SYMLINKS_DEFAULT)?);
let s2 = sig(os::stat(f2.as_ref(), FOLLOW_SYMLINKS_DEFAULT)?);
if s1.s_ifmt != stat::S_IFREG || s2.s_ifmt != stat::S_IFREG {
return Ok(false);
}
if shallow && s1 == s2 {
return Ok(true);
}
if s1.st_size != s2.st_size {
return Ok(false);
}
let key = (f1.as_ref().into(), f2.as_ref().into(), s1, s2);
let c_cache = Arc::clone(&CACHE);
let outcome = c_cache.lock().unwrap().get(&key).copied();
let outcome = if let Some(outcome) = outcome {
outcome
} else {
let outcome = do_cmp(f1, f2)?;
if c_cache.lock().unwrap().len() > MAX_CACHE_SIZE {
clear_cache();
}
c_cache.lock().unwrap().insert(key, outcome);
outcome
};
Ok(outcome)
}
pub fn cmpfiles<A, B, C, D>(
_dir1: A,
_dir2: B,
_common: D,
_shallow: bool,
) -> io::Result<(Vec<PathBuf>, Vec<PathBuf>, Vec<PathBuf>)>
where
A: AsRef<Path>,
B: AsRef<Path>,
C: AsRef<Path>,
D: AsRef<[C]>,
{
unimplemented!()
}
pub struct DirCmp;
impl DirCmp {
pub fn new(_a: impl AsRef<Path>, _b: impl AsRef<Path>) -> Self {
unimplemented!()
}
pub fn report_full_closure(&self) {
unimplemented!()
}
pub fn report(&self) {
unimplemented!()
}
}
fn sig(st: os::StatResult) -> Signature {
Signature {
s_ifmt: stat::S_IFMT(st.st_mode),
st_size: st.st_size,
st_mtime: st.st_mtime,
}
}
fn do_cmp(f1: impl AsRef<Path>, f2: impl AsRef<Path>) -> io::Result<bool> {
let mut f1 = File::open(f1.as_ref())?;
let mut f2 = File::open(f2.as_ref())?;
loop {
let mut buf1: [u8; BUFSIZE] = [0; BUFSIZE];
let mut buf2: [u8; BUFSIZE] = [0; BUFSIZE];
let len1 = f1.read(&mut buf1)?;
let len2 = f2.read(&mut buf2)?;
if len1 != len2 {
return Ok(false);
}
let read_size = len1;
if read_size == 0 {
return Ok(true);
}
if &buf1[..read_size] != &buf2[..read_size] {
return Ok(false);
}
}
}
#[derive(Debug)]
struct Signature {
s_ifmt: u32,
st_size: u64,
st_mtime: f64,
}
impl Signature {
fn canonicalize(&self) -> (u32, u64, [u8; 8]) {
(self.s_ifmt, self.st_size, self.st_mtime.to_ne_bytes())
}
}
impl PartialEq for Signature {
fn eq(&self, other: &Self) -> bool {
self.canonicalize() == other.canonicalize()
}
}
impl Eq for Signature {}
impl Hash for Signature {
fn hash<H>(&self, state: &mut H)
where
H: Hasher,
{
self.canonicalize().hash(state);
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::env;
use std::fs::File;
use std::io::Write;
#[test]
fn test_stat() {
let temp_dir = env::temp_dir();
let mut foo_path = temp_dir.clone();
let mut bar_path = temp_dir.clone();
let mut baz_path = temp_dir.clone();
foo_path.push("foo.txt");
bar_path.push("bar.txt");
baz_path.push("baz.txt");
let mut foo = File::create(&foo_path).unwrap(); let mut bar = File::create(&bar_path).unwrap(); let mut baz = File::create(&baz_path).unwrap();
let buf_digit: &[u8; 10] = b"0123456789";
let buf_alphabet: &[u8; 6] = b"abcdeg";
let buf: &[u8] = &[&buf_digit[..], &buf_alphabet[..]].concat();
foo.write(buf).unwrap();
bar.write(buf).unwrap();
baz.write(buf_digit).unwrap();
let shallow = true;
assert!(cmp(&foo_path, &foo_path, shallow).unwrap());
assert!(cmp(&bar_path, &bar_path, shallow).unwrap());
assert!(cmp(&baz_path, &baz_path, shallow).unwrap());
assert!(cmp(&foo_path, &bar_path, shallow).unwrap());
assert!(!cmp(&foo_path, &baz_path, shallow).unwrap());
assert!(!cmp(&bar_path, &baz_path, shallow).unwrap());
let shallow = false;
assert!(cmp(&foo_path, &foo_path, shallow).unwrap());
assert!(cmp(&bar_path, &bar_path, shallow).unwrap());
assert!(cmp(&baz_path, &baz_path, shallow).unwrap());
assert!(cmp(&foo_path, &bar_path, shallow).unwrap());
assert!(!cmp(&foo_path, &baz_path, shallow).unwrap());
assert!(!cmp(&bar_path, &baz_path, shallow).unwrap());
}
}