persist_if_changed/
lib.rs1use fs_err::File;
6use sha2::Digest;
7use std::io::ErrorKind;
8use std::{
9 io::{Read, Write},
10 path::Path,
11};
12
13#[tracing::instrument(skip_all, level=tracing::Level::TRACE)]
19pub fn persist_if_changed(path: &Path, content: &[u8]) -> Result<(), anyhow::Error> {
20 let has_changed = has_changed_file2buffer(path, content).unwrap_or(true);
21 if !has_changed {
22 return Ok(());
23 }
24 let mut file = fs_err::OpenOptions::new()
25 .write(true)
26 .truncate(true)
27 .create(true)
28 .open(path)?;
29 file.write_all(content)?;
30 Ok(())
31}
32
33#[tracing::instrument(skip_all, level=tracing::Level::TRACE)]
36pub fn copy_if_changed(from: &Path, to: &Path) -> Result<(), anyhow::Error> {
37 let has_changed = has_changed_file2file(from, to).unwrap_or(true);
38 if !has_changed {
39 return Ok(());
40 }
41 fs_err::copy(from, to)?;
42 Ok(())
43}
44
45#[tracing::instrument(skip_all, level=tracing::Level::TRACE)]
50pub fn has_changed_file2file(from: &Path, to: &Path) -> Result<bool, anyhow::Error> {
51 let from_file = File::open(from);
52 let to_file = File::open(to);
53 let (from_file, to_file) = match (from_file, to_file) {
54 (Ok(from_file), Ok(to_file)) => (from_file, to_file),
55 (Ok(_), Err(e)) | (Err(e), Ok(_)) => {
56 if e.kind() == ErrorKind::NotFound {
57 return Ok(true);
58 }
59 return Err(e.into());
60 }
61 (Err(e1), Err(e2)) => {
62 if e1.kind() == ErrorKind::NotFound && e2.kind() == ErrorKind::NotFound {
63 return Ok(false);
64 }
65 return Err(e1.into());
66 }
67 };
68
69 let from_metadata = from_file.metadata()?;
72 let to_metadata = to_file.metadata()?;
73 if from_metadata.len() != to_metadata.len() {
74 return Ok(true);
75 }
76
77 let from_checksum = compute_file_checksum(from_file)?;
78 let to_checksum = compute_file_checksum(to_file)?;
79 Ok(from_checksum != to_checksum)
80}
81
82#[tracing::instrument(skip_all, level=tracing::Level::TRACE)]
87pub fn has_changed_file2buffer(path: &Path, contents: &[u8]) -> Result<bool, anyhow::Error> {
88 let file = match File::open(path) {
89 Ok(f) => f,
90 Err(e) => {
91 if e.kind() == ErrorKind::NotFound {
92 return Ok(true);
93 }
94 return Err(e.into());
95 }
96 };
97 let metadata = file.metadata()?;
100 if metadata.len() != contents.len() as u64 {
101 return Ok(true);
102 }
103 let file_checksum = compute_file_checksum(file)?;
104 let buffer_checksum = compute_buffer_checksum(contents);
105 Ok(file_checksum != buffer_checksum)
106}
107
108#[tracing::instrument(skip_all, level=tracing::Level::TRACE)]
110fn compute_file_checksum(file: fs_err::File) -> std::io::Result<String> {
111 let mut hasher = sha2::Sha256::new();
112
113 let mut reader = std::io::BufReader::new(file);
114 let mut buffer = [0; 8192]; loop {
117 let bytes_read = reader.read(&mut buffer)?;
118 if bytes_read == 0 {
119 break;
120 }
121 hasher.update(&buffer[..bytes_read]);
122 }
123
124 let result = hasher.finalize();
125 Ok(format!("{:x}", result))
126}
127
128#[tracing::instrument(skip_all, level=tracing::Level::TRACE)]
130fn compute_buffer_checksum(buffer: &[u8]) -> String {
131 let mut hasher = sha2::Sha256::new();
132 hasher.update(buffer);
133 let result = hasher.finalize();
134 format!("{:x}", result)
135}