1use log::*;
2use std::fs::copy;
3use std::io::{Error, ErrorKind};
4use std::path::{Path, PathBuf};
5use std::time::SystemTime;
6use walkdir::WalkDir;
7
8#[derive(Debug, Clone)]
9pub struct CopyBuilder {
32 pub source: PathBuf,
34 pub destination: PathBuf,
36 overwrite_all: bool,
37 overwrite_if_newer: bool,
38 overwrite_if_size_differs: bool,
39 exclude_filters: Vec<String>,
40 include_filters: Vec<String>,
41}
42
43fn is_file_newer(file_a: &Path, file_b: &Path) -> bool {
45 match (file_a.metadata(), file_b.metadata()) {
46 (Ok(meta_a), Ok(meta_b)) => {
47 meta_a.modified().unwrap_or_else(|_| SystemTime::now())
48 > meta_b.modified().unwrap_or(SystemTime::UNIX_EPOCH)
49 }
50 _ => false,
51 }
52}
53
54fn is_filesize_different(file_a: &Path, file_b: &Path) -> bool {
56 match (file_a.metadata(), file_b.metadata()) {
57 (Ok(meta_a), Ok(meta_b)) => meta_a.len() != meta_b.len(),
58 _ => false,
59 }
60}
61
62impl CopyBuilder {
63
64 pub fn new<P: AsRef<Path>, Q: AsRef<Path>>(source: P, dest: Q) -> CopyBuilder {
66 CopyBuilder {
67 source: source.as_ref().to_path_buf(),
68 destination: dest.as_ref().to_path_buf(),
69 overwrite_all: false,
70 overwrite_if_newer: false,
71 overwrite_if_size_differs: false,
72 exclude_filters: vec![],
73 include_filters: vec![],
74 }
75 }
76
77 pub fn overwrite(self, overwrite: bool) -> CopyBuilder {
79 CopyBuilder {
80 overwrite_all: overwrite,
81 ..self
82 }
83 }
84
85 pub fn overwrite_if_newer(self, overwrite_only_newer: bool) -> CopyBuilder {
87 CopyBuilder {
88 overwrite_if_newer: overwrite_only_newer,
89 ..self
90 }
91 }
92
93 pub fn overwrite_if_size_differs(self, overwrite_if_size_differs: bool) -> CopyBuilder {
95 CopyBuilder {
96 overwrite_if_size_differs,
97 ..self
98 }
99 }
100
101 pub fn with_exclude_filter(self, f: &str) -> CopyBuilder {
103 let mut filters = self.exclude_filters.clone();
104 filters.push(f.to_owned());
105 CopyBuilder {
106 exclude_filters: filters,
107 ..self
108 }
109 }
110
111 pub fn with_include_filter(self, f: &str) -> CopyBuilder {
113 let mut filters = self.exclude_filters.clone();
114 filters.push(f.to_owned());
115 CopyBuilder {
116 include_filters: filters,
117 ..self
118 }
119 }
120
121 pub fn run(&self) -> Result<(), std::io::Error> {
123 if !self.destination.is_dir() {
124 debug!("MKDIR {:?}", &self.destination);
125 std::fs::create_dir_all(&self.destination)?;
126 }
127 let abs_source = self.source.canonicalize()?;
128 let abs_dest = self.destination.canonicalize()?;
129 debug!(
130 "Building copy operation: SRC {} DST {}",
131 abs_source.display(),
132 abs_dest.display()
133 );
134
135 for entry in WalkDir::new(&abs_source).into_iter().filter_map(|e| e.ok()) {
136 let rel_dest = entry.path().strip_prefix(&abs_source).map_err(|e| {
137 Error::new(ErrorKind::Other, format!("Could not strip prefix: {:?}", e))
138 })?;
139 let dest_entry = abs_dest.join(rel_dest);
140
141 if entry.path().is_file() {
142 if !self.overwrite_all
146 && dest_entry.is_file()
147 && !self.overwrite_if_newer
148 && !self.overwrite_if_size_differs
149 {
150 continue;
151 }
152
153 for f in &self.exclude_filters {
154 if entry.path().to_string_lossy().contains(f) {
155 continue;
156 }
157 }
158
159 for f in &self.include_filters {
160 if !entry.path().to_string_lossy().contains(f) {
161 continue;
162 }
163 }
164
165 if !dest_entry.is_file() {
167 debug!(
168 "Dest not present: CP {} DST {}",
169 entry.path().display(),
170 dest_entry.display()
171 );
172 copy(entry.path(), dest_entry)?;
173 continue;
174 }
175
176 if self.overwrite_if_newer {
178 if is_file_newer(entry.path(), &dest_entry) {
179 debug!(
180 "Source newer: CP {} DST {}",
181 entry.path().display(),
182 dest_entry.display()
183 );
184 copy(entry.path(), &dest_entry)?;
185 }
186 continue;
187 }
188
189 if self.overwrite_if_size_differs {
191 if is_filesize_different(entry.path(), &dest_entry) {
192 debug!(
193 "Source differs: CP {} DST {}",
194 entry.path().display(),
195 dest_entry.display()
196 );
197 copy(entry.path(), &dest_entry)?;
198 }
199 continue;
200 }
201
202 debug!("CP {} DST {}", entry.path().display(), dest_entry.display());
204 copy(entry.path(), dest_entry)?;
205 } else if entry.path().is_dir() && !dest_entry.is_dir() {
206 debug!("MKDIR {}", entry.path().display());
207 std::fs::create_dir_all(dest_entry)?;
208 }
209 }
210
211 Ok(())
212 }
213}
214
215pub fn copy_dir_advanced<P: AsRef<Path>, Q: AsRef<Path>>(source: P, dest: Q, overwrite_all: bool, overwrite_if_newer: bool, overwrite_if_size_differs: bool, exclude_filters: Vec<String>, include_filters: Vec<String>) -> Result<(), std::io::Error>{
217 CopyBuilder {
218 source: source.as_ref().to_path_buf(),
219 destination: dest.as_ref().to_path_buf(),
220 overwrite_all,
221 overwrite_if_newer,
222 overwrite_if_size_differs,
223 exclude_filters,
224 include_filters,
225 }.run()
226}
227
228pub fn copy_dir<P: AsRef<Path>, Q: AsRef<Path>>(source: P, dest: Q) -> Result<(), std::io::Error>{
230 CopyBuilder {
231 source: source.as_ref().to_path_buf(),
232 destination: dest.as_ref().to_path_buf(),
233 overwrite_all: false,
234 overwrite_if_newer: false,
235 overwrite_if_size_differs: false,
236 exclude_filters: vec![],
237 include_filters: vec![],
238 }.run()
239}