lms_lib/
core.rs

1//! Contains core copy, remove, synchronize functions
2
3use std::io;
4
5use rayon::prelude::*;
6
7use crate::{file_ops, file_ops::Dir, parse::Flag};
8
9/// Synchronizes all files, directories, and symlinks in `dest` with `src`
10///
11/// # Arguments
12/// * `src`: Source directory
13/// * `dest`: Destination directory
14/// * `flags`: set for Flag's
15///
16/// # Errors
17/// This function will return an error in the following situations,
18/// but is not limited to just these cases:
19/// * `src` is an invalid directory
20/// * `dest` is an invalid directory
21pub fn synchronize(src: &str, dest: &str, flags: Flag) -> Result<(), io::Error> {
22    // Retrieve data from src directory about files, dirs, symlinks
23    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    // Retrieve data from dest directory about files, dirs, symlinks
29    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    // Determine whether or not to delete
35    let delete = !flags.contains(Flag::NO_DELETE);
36
37    // Delete files and symlinks
38    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    // Delete dirs in the correct order
57    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
66/// Copies all files, directories, and symlinks in `src` to `dest`
67///
68/// # Arguments
69/// * `src`: Source directory
70/// * `dest`: Destination directory
71/// * `flags`: set for Flag's
72///
73/// # Errors
74/// This function will return an error in the following situations,
75/// but is not limited to just these cases:
76/// * `src` is an invalid directory
77/// * `dest` is an invalid directory
78pub fn copy(src: &str, dest: &str, _flags: Flag) -> Result<(), io::Error> {
79    // Retrieve data from src directory about files, dirs, symlinks
80    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    // Copy everything
86    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
93/// Deletes directory `target`
94///
95/// # Arguments
96/// * `target`: Target directory
97/// * `flags`: set for Flag's
98///
99/// # Errors
100/// This function will return an error in the following situations,
101/// but is not limited to just these cases:
102/// * `target` is an invalid directory
103pub fn remove(target: &str, _flags: Flag) -> Result<(), io::Error> {
104    // Retrieve data from target directory about files, dirs, symlinks
105    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    // Delete everything
111    file_ops::delete_files(target_files.into_par_iter(), target);
112    file_ops::delete_files(target_symlinks.into_par_iter(), target);
113
114    // Directories must always be deleted sequentially so that they are deleted in the correct order
115    let mut target_dirs: Vec<&file_ops::Dir> = file_ops::sort_files(target_dirs.into_par_iter());
116
117    // Delete the target directory last
118    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///////////////////////////////////////////////////////////////////////////////////////////////////
127// Tests
128///////////////////////////////////////////////////////////////////////////////////////////////////
129
130#[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}