persist_if_changed/
lib.rsuse fs_err::File;
use sha2::Digest;
use std::io::ErrorKind;
use std::{
io::{Read, Write},
path::Path,
};
#[tracing::instrument(skip_all, level=tracing::Level::TRACE)]
pub fn persist_if_changed(path: &Path, content: &[u8]) -> Result<(), anyhow::Error> {
let has_changed = has_changed_file2buffer(path, content).unwrap_or(true);
if !has_changed {
return Ok(());
}
let mut file = fs_err::OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(path)?;
file.write_all(content)?;
Ok(())
}
#[tracing::instrument(skip_all, level=tracing::Level::TRACE)]
pub fn copy_if_changed(from: &Path, to: &Path) -> Result<(), anyhow::Error> {
let has_changed = has_changed_file2file(from, to).unwrap_or(true);
if !has_changed {
return Ok(());
}
fs_err::copy(from, to)?;
Ok(())
}
#[tracing::instrument(skip_all, level=tracing::Level::TRACE)]
pub fn has_changed_file2file(from: &Path, to: &Path) -> Result<bool, anyhow::Error> {
let from_file = File::open(from);
let to_file = File::open(to);
let (from_file, to_file) = match (from_file, to_file) {
(Ok(from_file), Ok(to_file)) => (from_file, to_file),
(Ok(_), Err(e)) | (Err(e), Ok(_)) => {
if e.kind() == ErrorKind::NotFound {
return Ok(true);
}
return Err(e.into());
}
(Err(e1), Err(e2)) => {
if e1.kind() == ErrorKind::NotFound && e2.kind() == ErrorKind::NotFound {
return Ok(false);
}
return Err(e1.into());
}
};
let from_metadata = from_file.metadata()?;
let to_metadata = to_file.metadata()?;
if from_metadata.len() != to_metadata.len() {
return Ok(true);
}
let from_checksum = compute_file_checksum(from_file)?;
let to_checksum = compute_file_checksum(to_file)?;
Ok(from_checksum != to_checksum)
}
#[tracing::instrument(skip_all, level=tracing::Level::TRACE)]
pub fn has_changed_file2buffer(path: &Path, contents: &[u8]) -> Result<bool, anyhow::Error> {
let file = match File::open(path) {
Ok(f) => f,
Err(e) => {
if e.kind() == ErrorKind::NotFound {
return Ok(true);
}
return Err(e.into());
}
};
let metadata = file.metadata()?;
if metadata.len() != contents.len() as u64 {
return Ok(true);
}
let file_checksum = compute_file_checksum(file)?;
let buffer_checksum = compute_buffer_checksum(contents);
Ok(file_checksum != buffer_checksum)
}
#[tracing::instrument(skip_all, level=tracing::Level::TRACE)]
fn compute_file_checksum(file: fs_err::File) -> std::io::Result<String> {
let mut hasher = sha2::Sha256::new();
let mut reader = std::io::BufReader::new(file);
let mut buffer = [0; 8192]; loop {
let bytes_read = reader.read(&mut buffer)?;
if bytes_read == 0 {
break;
}
hasher.update(&buffer[..bytes_read]);
}
let result = hasher.finalize();
Ok(format!("{:x}", result))
}
#[tracing::instrument(skip_all, level=tracing::Level::TRACE)]
fn compute_buffer_checksum(buffer: &[u8]) -> String {
let mut hasher = sha2::Sha256::new();
hasher.update(buffer);
let result = hasher.finalize();
format!("{:x}", result)
}