1use std::io;
4
5use rayon::prelude::*;
6
7use crate::{file_ops, file_ops::Dir, parse::Flag};
8
9pub fn synchronize(src: &str, dest: &str, flags: Flag) -> Result<(), io::Error> {
22 let src_file_sets = file_ops::get_all_files(src)?;
24 let src_files = src_file_sets.files();
25 let src_dirs = src_file_sets.dirs();
26 let src_symlinks = src_file_sets.symlinks();
27
28 let dest_file_sets = file_ops::get_all_files(dest)?;
30 let dest_files = dest_file_sets.files();
31 let dest_dirs = dest_file_sets.dirs();
32 let dest_symlinks = dest_file_sets.symlinks();
33
34 let delete = !flags.contains(Flag::NO_DELETE);
36
37 if delete {
39 let symlinks_to_delete = dest_symlinks.par_difference(src_symlinks);
40 let files_to_delete = dest_files.par_difference(src_files);
41
42 file_ops::delete_files(symlinks_to_delete, dest);
43 file_ops::delete_files(files_to_delete, dest);
44 }
45
46 let dirs_to_copy = src_dirs.par_difference(dest_dirs);
47 let symlinks_to_copy = src_symlinks.par_difference(dest_symlinks);
48 let files_to_copy = src_files.par_difference(dest_files);
49 let files_to_compare = src_files.par_intersection(dest_files);
50
51 file_ops::copy_files(dirs_to_copy, src, dest);
52 file_ops::copy_files(symlinks_to_copy, src, dest);
53 file_ops::copy_files(files_to_copy, src, dest);
54 file_ops::compare_and_copy_files(files_to_compare, src, dest, flags);
55
56 if delete {
58 let dirs_to_delete = dest_dirs.par_difference(src_dirs);
59 let dirs_to_delete: Vec<&file_ops::Dir> = file_ops::sort_files(dirs_to_delete);
60 file_ops::delete_files_sequential(dirs_to_delete, dest);
61 }
62
63 Ok(())
64}
65
66pub fn copy(src: &str, dest: &str, _flags: Flag) -> Result<(), io::Error> {
79 let src_file_sets = file_ops::get_all_files(src)?;
81 let src_files = src_file_sets.files();
82 let src_dirs = src_file_sets.dirs();
83 let src_symlinks = src_file_sets.symlinks();
84
85 file_ops::copy_files(src_dirs.into_par_iter(), src, dest);
87 file_ops::copy_files(src_files.into_par_iter(), src, dest);
88 file_ops::copy_files(src_symlinks.into_par_iter(), src, dest);
89
90 Ok(())
91}
92
93pub fn remove(target: &str, _flags: Flag) -> Result<(), io::Error> {
104 let target_file_sets = file_ops::get_all_files(target)?;
106 let target_files = target_file_sets.files();
107 let target_dirs = target_file_sets.dirs();
108 let target_symlinks = target_file_sets.symlinks();
109
110 file_ops::delete_files(target_files.into_par_iter(), target);
112 file_ops::delete_files(target_symlinks.into_par_iter(), target);
113
114 let mut target_dirs: Vec<&file_ops::Dir> = file_ops::sort_files(target_dirs.into_par_iter());
116
117 let root_dir = Dir::from("");
119 target_dirs.push(&root_dir);
120
121 file_ops::delete_files_sequential(target_dirs.into_iter(), target);
122
123 Ok(())
124}
125
126#[cfg(test)]
131mod test_synchronize {
132 use super::*;
133 use std::fs;
134 use std::process::Command;
135
136 #[cfg(debug_assertions)]
137 const BUILD_DIR: &str = "target/debug";
138
139 #[cfg(not(debug_assertions))]
140 const BUILD_DIR: &str = "target/release";
141
142 #[test]
143 fn invalid_src() {
144 assert_eq!(synchronize("/?", "src", Flag::empty()).is_err(), true);
145 }
146
147 #[test]
148 fn invalid_dest() {
149 assert_eq!(synchronize("src", "/?", Flag::empty()).is_err(), true);
150 }
151
152 #[cfg(target_family = "unix")]
153 #[test]
154 fn dir_1() {
155 const TEST_DIR: &str = "test_synchronize_dir1";
156 fs::create_dir_all(TEST_DIR).unwrap();
157
158 assert_eq!(synchronize("src", TEST_DIR, Flag::empty()).is_ok(), true);
159
160 let diff = Command::new("diff")
161 .args(&["-r", "src", TEST_DIR])
162 .output()
163 .unwrap();
164
165 assert_eq!(diff.status.success(), true);
166
167 fs::remove_dir_all(TEST_DIR).unwrap();
168 }
169
170 #[cfg(target_family = "unix")]
171 #[test]
172 fn dir_2() {
173 const TEST_DIR: &str = "test_synchronize_dir2";
174 fs::create_dir_all(TEST_DIR).unwrap();
175
176 assert_eq!(
177 synchronize(BUILD_DIR, TEST_DIR, Flag::empty()).is_ok(),
178 true
179 );
180
181 let diff = Command::new("diff")
182 .args(&["-r", BUILD_DIR, TEST_DIR])
183 .output()
184 .unwrap();
185
186 assert_eq!(diff.status.success(), true);
187
188 fs::File::create([BUILD_DIR, "file.txt"].join("/")).unwrap();
189 fs::remove_dir_all([BUILD_DIR, "build"].join("/")).unwrap();
190
191 let diff = Command::new("diff")
192 .args(&["-r", BUILD_DIR, TEST_DIR])
193 .output()
194 .unwrap();
195
196 assert_eq!(diff.status.success(), false);
197
198 assert_eq!(
199 synchronize(BUILD_DIR, TEST_DIR, Flag::empty()).is_ok(),
200 true
201 );
202
203 let diff = Command::new("diff")
204 .args(&["-r", BUILD_DIR, TEST_DIR])
205 .output()
206 .unwrap();
207
208 assert_eq!(diff.status.success(), true);
209
210 fs::remove_dir_all(TEST_DIR).unwrap();
211 }
212
213 #[cfg(target_family = "unix")]
214 #[test]
215 fn change_symlink() {
216 use std::os::unix::fs::symlink;
217
218 const TEST_SRC: &str = "test_synchronize_change_symlink_src";
219 const TEST_DEST: &str = "test_synchronize_change_symlink_dest";
220 fs::create_dir_all(TEST_SRC).unwrap();
221 fs::create_dir_all(TEST_DEST).unwrap();
222
223 symlink("../Cargo.lock", [TEST_SRC, "file"].join("/")).unwrap();
224 symlink("../Cargo.toml", [TEST_DEST, "file"].join("/")).unwrap();
225
226 let diff = Command::new("diff")
227 .args(&["-r", TEST_SRC, TEST_DEST])
228 .output()
229 .unwrap();
230
231 assert_eq!(diff.status.success(), false);
232
233 assert_eq!(
234 synchronize(TEST_SRC, TEST_DEST, Flag::empty()).is_ok(),
235 true
236 );
237
238 let diff = Command::new("diff")
239 .args(&["-r", TEST_SRC, TEST_DEST])
240 .output()
241 .unwrap();
242
243 assert_eq!(diff.status.success(), true);
244
245 fs::remove_dir_all(TEST_DEST).unwrap();
246 fs::remove_dir_all(TEST_SRC).unwrap();
247 }
248
249 #[cfg(target_family = "unix")]
250 #[test]
251 fn flags() {
252 const TEST_DIR: &str = "test_synchronize_flags";
253 const TEST_DIR_OUT: &str = "test_synchronize_flags_out";
254 const TEST_DIR_EXPECTED: &str = "test_synchronize_flags_expected";
255 const TEST_FILES: [&str; 2] = ["file1.txt", "file2.txt"];
256
257 fs::create_dir_all(TEST_DIR).unwrap();
258 fs::create_dir_all(TEST_DIR_OUT).unwrap();
259 fs::create_dir_all(TEST_DIR_EXPECTED).unwrap();
260
261 fs::File::create([TEST_DIR, TEST_FILES[0]].join("/")).unwrap();
262 fs::File::create([TEST_DIR_EXPECTED, TEST_FILES[0]].join("/")).unwrap();
263 fs::File::create([TEST_DIR_EXPECTED, TEST_FILES[1]].join("/")).unwrap();
264
265 assert_eq!(
266 synchronize(TEST_DIR, TEST_DIR_OUT, Flag::empty()).is_ok(),
267 true
268 );
269
270 fs::File::create([TEST_DIR, TEST_FILES[1]].join("/")).unwrap();
271
272 let mut flags = Flag::empty();
273 flags.insert(Flag::VERBOSE);
274 flags.insert(Flag::NO_DELETE);
275 flags.insert(Flag::SECURE);
276 flags.insert(Flag::SEQUENTIAL);
277
278 assert_eq!(synchronize(TEST_DIR, TEST_DIR_OUT, flags).is_ok(), true);
279
280 let diff = Command::new("diff")
281 .args(&["-r", TEST_DIR_OUT, TEST_DIR_EXPECTED])
282 .output()
283 .unwrap();
284
285 assert_eq!(diff.status.success(), true);
286
287 fs::remove_dir_all(TEST_DIR).unwrap();
288 fs::remove_dir_all(TEST_DIR_OUT).unwrap();
289 fs::remove_dir_all(TEST_DIR_EXPECTED).unwrap();
290 }
291}
292
293#[cfg(test)]
294mod test_copy {
295 use super::*;
296 use std::fs;
297 use std::process::Command;
298
299 #[test]
300 fn invalid_src() {
301 assert_eq!(copy("/?", "src", Flag::empty()).is_err(), true);
302 }
303
304 #[test]
305 fn invalid_dest() {
306 const TEST_DIR: &str = "test_copy_invalid_dest";
307 assert_eq!(copy("src", TEST_DIR, Flag::empty()).is_ok(), true);
308 fs::remove_dir_all(TEST_DIR).unwrap();
309 }
310
311 #[cfg(target_family = "unix")]
312 #[test]
313 fn dir1() {
314 const TEST_DIR: &str = "test_copy_dir1";
315 fs::create_dir_all(TEST_DIR).unwrap();
316
317 assert_eq!(copy("src", TEST_DIR, Flag::empty()).is_ok(), true);
318
319 let diff = Command::new("diff")
320 .args(&["-r", "src", TEST_DIR])
321 .output()
322 .unwrap();
323
324 assert_eq!(diff.status.success(), true);
325
326 fs::remove_dir_all(TEST_DIR).unwrap();
327 }
328
329 #[cfg(target_family = "unix")]
330 #[test]
331 fn flags() {
332 const TEST_DIR: &str = "test_copy_flags";
333 fs::create_dir_all(TEST_DIR).unwrap();
334
335 let mut flags = Flag::empty();
336 flags.insert(Flag::SEQUENTIAL);
337
338 assert_eq!(copy("src", TEST_DIR, flags).is_ok(), true);
339
340 let diff = Command::new("diff")
341 .args(&["-r", "src", TEST_DIR])
342 .output()
343 .unwrap();
344
345 assert_eq!(diff.status.success(), true);
346
347 fs::remove_dir_all(TEST_DIR).unwrap();
348 }
349}
350
351#[cfg(test)]
352mod test_remove {
353 use super::*;
354 use std::fs;
355 use std::process::Command;
356
357 #[cfg(debug_assertions)]
358 const BUILD_DIR: &str = "target/debug";
359
360 #[cfg(not(debug_assertions))]
361 const BUILD_DIR: &str = "target/release";
362
363 #[test]
364 fn invalid_target() {
365 assert_eq!(remove("/?", Flag::empty()).is_err(), true);
366 }
367
368 #[cfg(target_family = "unix")]
369 #[test]
370 fn dir1() {
371 const TEST_DIR: &str = "test_remove_dir1";
372 fs::create_dir_all(TEST_DIR).unwrap();
373
374 Command::new("cp")
375 .args(&["-r", BUILD_DIR, TEST_DIR])
376 .output()
377 .unwrap();
378
379 assert_eq!(remove(TEST_DIR, Flag::empty()).is_ok(), true);
380
381 assert_eq!(fs::read_dir(TEST_DIR).is_err(), true);
382 }
383
384 #[cfg(target_family = "unix")]
385 #[test]
386 fn flags() {
387 const TEST_DIR: &str = "test_remove_flags";
388 fs::create_dir_all(TEST_DIR).unwrap();
389
390 let mut flags = Flag::empty();
391 flags.insert(Flag::SEQUENTIAL);
392
393 Command::new("cp")
394 .args(&["-r", "src", TEST_DIR])
395 .output()
396 .unwrap();
397
398 assert_eq!(remove(TEST_DIR, flags).is_ok(), true);
399
400 assert_eq!(fs::read_dir(TEST_DIR).is_err(), true);
401 }
402}