1use std::fs::{self, File};
4use std::io::{self, Read};
5use std::path::Path;
6
7use anyhow::Result;
8
9pub fn copy_file_if_different(
15 src_file: impl AsRef<Path>,
16 dest_file_or_dir: impl AsRef<Path>,
17) -> Result<()> {
18 let src_file: &Path = src_file.as_ref();
19 let dest_file_or_dir: &Path = dest_file_or_dir.as_ref();
20
21 assert!(src_file.is_file());
22
23 let src_fd = fs::File::open(src_file)?;
24
25 let (dest_fd, dest_file) = if dest_file_or_dir.exists() {
26 if dest_file_or_dir.is_dir() {
27 let dest_file = dest_file_or_dir.join(src_file.file_name().unwrap());
28 if dest_file.exists() {
29 (Some(fs::File::open(&dest_file)?), dest_file)
30 } else {
31 (None, dest_file)
32 }
33 } else {
34 (
35 Some(fs::File::open(dest_file_or_dir)?),
36 dest_file_or_dir.to_owned(),
37 )
38 }
39 } else {
40 (None, dest_file_or_dir.to_owned())
41 };
42
43 if let Some(dest_fd) = dest_fd {
44 if !is_file_eq(&src_fd, &dest_fd)? {
45 drop(dest_fd);
46 drop(src_fd);
47 copy_with_metadata(src_file, dest_file)?;
48 }
49 } else {
50 copy_with_metadata(src_file, dest_file)?;
51 }
52 Ok(())
53}
54
55pub fn is_file_eq(file: &File, other: &File) -> Result<bool> {
57 let file_meta = file.metadata()?;
58 let other_meta = other.metadata()?;
59
60 if file_meta.file_type() == other_meta.file_type()
61 && file_meta.len() == other_meta.len()
62 && file_meta.modified()? == other_meta.modified()?
63 {
64 let mut file_bytes = io::BufReader::new(file).bytes();
65 let mut other_bytes = io::BufReader::new(other).bytes();
66
67 loop {
69 match (file_bytes.next(), other_bytes.next()) {
70 (Some(Ok(b0)), Some(Ok(b1))) => {
71 if b0 != b1 {
72 break Ok(false);
73 }
74 }
75 (None, None) => break Ok(true),
76 (None, Some(_)) | (Some(_), None) => break Ok(false),
77 (Some(Err(e)), _) | (_, Some(Err(e))) => return Err(e.into()),
78 }
79 }
80 } else {
81 Ok(false)
82 }
83}
84
85pub fn copy_with_metadata(src_file: impl AsRef<Path>, dest_file: impl AsRef<Path>) -> Result<()> {
93 fs::copy(&src_file, &dest_file)?;
94 let src_file_meta = fs::File::open(&src_file)?.metadata()?;
95
96 let src_atime = filetime::FileTime::from_last_access_time(&src_file_meta);
97 let src_mtime = filetime::FileTime::from_last_modification_time(&src_file_meta);
98
99 fs::set_permissions(dest_file.as_ref(), src_file_meta.permissions())?;
100 filetime::set_file_times(dest_file, src_atime, src_mtime)?;
101
102 Ok(())
103}