diff --git a/Cargo.toml b/Cargo.toml index e27d3b3..ec7d74c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,3 +28,4 @@ bindgen = "0.57" xmas-elf = "0.8" bitflags = "1.3" shlex = "1.0" +remove_dir_all = "0.7" diff --git a/src/fs.rs b/src/fs.rs new file mode 100644 index 0000000..52bc1bc --- /dev/null +++ b/src/fs.rs @@ -0,0 +1,88 @@ +//! Filesystem utilities + +use std::fs::{self, File}; +use std::io::{self, Read, Seek}; +use std::path::Path; + +use anyhow::Result; + +pub use remove_dir_all::*; + +/// Copy `src_file` to `dest_file_or_dir` if `src_file` is different or the destination +/// file doesn't exist. +pub fn copy_file_if_different( + src_file: impl AsRef, + dest_file_or_dir: impl AsRef, +) -> Result<()> { + let src_file: &Path = src_file.as_ref(); + let dest_file_or_dir: &Path = dest_file_or_dir.as_ref(); + + assert!(src_file.is_file()); + + let mut src_fd = fs::File::open(src_file)?; + + let (dest_fd, dest_file) = if dest_file_or_dir.exists() { + if dest_file_or_dir.is_dir() { + let dest_file = dest_file_or_dir.join(src_file.file_name().unwrap()); + if dest_file.exists() { + (Some(fs::File::open(&dest_file)?), dest_file) + } else { + (None, dest_file) + } + } else { + ( + Some(fs::File::open(dest_file_or_dir)?), + dest_file_or_dir.to_owned(), + ) + } + } else { + (None, dest_file_or_dir.to_owned()) + }; + + if let Some(mut dest_fd) = dest_fd { + if !is_file_eq(&mut src_fd, &mut dest_fd)? { + drop(dest_fd); + drop(src_fd); + fs::copy(src_file, dest_file)?; + } + } else { + fs::copy(src_file, dest_file)?; + } + Ok(()) +} + +/// Whether the file type and contents of `file` are equal to `other`. +pub fn is_file_eq(file: &mut File, other: &mut File) -> Result { + let file_meta = file.metadata()?; + let other_meta = other.metadata()?; + + if file_meta.file_type() == other_meta.file_type() && file_meta.len() == other_meta.len() { + let mut file_bytes = io::BufReader::new(&*file).bytes(); + let mut other_bytes = io::BufReader::new(&*other).bytes(); + + // TODO: check performance + let result = loop { + match (file_bytes.next(), other_bytes.next()) { + (Some(Ok(b0)), Some(Ok(b1))) => { + if b0 != b1 { + break Ok(false); + } + } + (None, None) => break Ok(true), + (None, Some(_)) | (Some(_), None) => break Ok(false), + (Some(Err(e)), _) | (_, Some(Err(e))) => return Err(e.into()), + } + }; + drop(file_bytes); + drop(other_bytes); + + // rewind files + // TODO: is this needed? + file.seek(io::SeekFrom::Start(0))?; + other.seek(io::SeekFrom::Start(0))?; + + result + } else { + Ok(false) + } +} diff --git a/src/lib.rs b/src/lib.rs index bf7ba3d..4ab6205 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,3 +8,4 @@ pub mod pio; pub mod python; pub mod symgen; pub mod utils; +pub mod fs;