use crate::config::RunMode;
use crate::error::*;
use rayon::prelude::*;
use std::fs;
use std::path::Path;
use std::path::PathBuf;
use walkdir::{DirEntry, WalkDir};
pub type PathList = Vec<PathBuf>;
pub fn get_paths(mode: &RunMode) -> PathList {
match mode {
RunMode::Recursive {
paths,
max_depth,
hidden,
} => {
let is_hidden = |f: &DirEntry| -> bool {
if !hidden && f.depth() > 0 {
f.file_name()
.to_str()
.map(|s| !s.starts_with('.'))
.unwrap_or(false)
} else {
true
}
};
let mut path_list = PathList::new();
for path in paths {
let walkdir = match max_depth {
Some(max_depth) => WalkDir::new(path).max_depth(*max_depth),
None => WalkDir::new(path),
};
let mut walk_list: PathList = walkdir
.into_iter()
.filter_entry(is_hidden)
.filter_map(|e| e.ok())
.map(|p| p.path().to_path_buf())
.collect();
path_list.append(&mut walk_list);
}
path_list
}
RunMode::Simple(path_list) => path_list.iter().map(PathBuf::from).collect(),
_ => PathList::new(),
}
}
pub fn get_unique_filename(path: &Path, suffix: &str) -> PathBuf {
let base_name = format!("{}{}", path.file_name().unwrap().to_string_lossy(), suffix);
let mut unique_name = path.to_path_buf();
unique_name.set_file_name(&base_name);
let mut index = 0;
while unique_name.symlink_metadata().is_ok() {
index += 1;
unique_name.set_file_name(format!("{}.{}", base_name, index));
}
unique_name
}
pub fn create_backup(path: &Path) -> Result<PathBuf> {
let backup = get_unique_filename(path, ".bk");
match fs::copy(path, &backup) {
Ok(_) => Ok(backup),
Err(_) => Err(Error {
kind: ErrorKind::CreateBackup,
value: Some(path.to_string_lossy().to_string()),
}),
}
}
pub fn cleanup_paths(paths: PathList, keep_dirs: bool) -> PathList {
let mut paths: PathList = paths
.into_par_iter()
.filter(|p| p.symlink_metadata().is_ok())
.filter(|p| {
if p.is_dir() {
keep_dirs && p.file_name().is_some()
} else {
true
}
})
.map(|p| p.clone())
.collect();
paths.par_sort_unstable();
paths.dedup();
paths
}
#[allow(dead_code)]
pub fn create_symlink(source: &Path, symlink_file: &Path) -> Result<()> {
#[cfg(windows)]
match ::std::os::windows::fs::symlink_file(source, symlink_file) {
Ok(_) => Ok(()),
Err(_) => Err(Error {
kind: ErrorKind::CreateSymlink,
value: Some(symlink_file.to_string_lossy().to_string()),
}),
}
#[cfg(unix)]
match ::std::os::unix::fs::symlink(source, symlink_file) {
Ok(_) => Ok(()),
Err(_) => Err(Error {
kind: ErrorKind::CreateSymlink,
value: Some(symlink_file.to_string_lossy().to_string()),
}),
}
}
pub fn is_same_file(source: &Path, target: &Path) -> bool {
#[cfg(any(windows, target_os = "macos"))]
{
let source_metadata = fs::File::open(&source).unwrap().metadata().unwrap();
let target_metadata = fs::File::open(&target).unwrap().metadata().unwrap();
let low_source = source.to_string_lossy().to_string().to_lowercase();
let low_target = target.to_string_lossy().to_string().to_lowercase();
return low_source == low_target
&& source_metadata.file_type() == target_metadata.file_type()
&& source_metadata.len() == target_metadata.len()
&& source_metadata.created().unwrap() == target_metadata.created().unwrap()
&& source_metadata.modified().unwrap() == target_metadata.modified().unwrap();
}
source == target
}
#[cfg(test)]
mod test {
extern crate tempfile;
use super::*;
use std::fs;
use std::io::prelude::*;
#[test]
fn backup() {
let tempdir = tempfile::tempdir().expect("Error creating temp directory");
println!("Running test in '{:?}'", tempdir);
let temp_path = tempdir.path().to_str().unwrap();
let mock_files: PathList = vec![
[temp_path, "test_file_1.txt"].iter().collect(),
[temp_path, "test_file_2.txt"].iter().collect(),
[temp_path, "test_file_3.txt"].iter().collect(),
];
for file in &mock_files {
fs::File::create(file).expect("Error creating mock file...");
create_backup(file).expect("Error generating backup file...");
}
let backup_files: PathList = vec![
[temp_path, "test_file_1.txt.bk"].iter().collect(),
[temp_path, "test_file_2.txt.bk"].iter().collect(),
[temp_path, "test_file_3.txt.bk"].iter().collect(),
];
for file in &backup_files {
println!("{}", file.display());
assert!(file.exists());
}
}
#[test]
fn unique_name() {
let tempdir = tempfile::tempdir().expect("Error creating temp directory");
println!("Running test in '{:?}'", tempdir);
let temp_path = tempdir.path().to_str().unwrap();
let mock_files: PathList = vec![
[temp_path, "test_file_1"].iter().collect(),
[temp_path, "test_file_1.1"].iter().collect(),
[temp_path, "test_file_1.2"].iter().collect(),
];
for file in &mock_files {
fs::File::create(file).expect("Error creating mock file...");
}
let symlink = PathBuf::from(format!("{}/test_file_1.3", temp_path));
create_symlink(&mock_files[0], &symlink).expect("Error creating symlink.");
let broken_symlink = PathBuf::from(format!("{}/test_file_1.4", temp_path));
create_symlink(&PathBuf::from("broken_link"), &broken_symlink)
.expect("Error creating broken symlink.");
let new_file: PathBuf = [temp_path, "test_file_1.5"].iter().collect();
assert_eq!(get_unique_filename(&mock_files[0], ""), new_file);
}
#[test]
fn get_file_list() {
let mock_files: Vec<String> = vec![
"test_file_1.txt".to_string(),
"test_file_2.txt".to_string(),
"test_file_3.txt".to_string(),
];
let mode = RunMode::Simple(mock_files);
let files = get_paths(&mode);
assert!(files.contains(&PathBuf::from("test_file_1.txt")));
assert!(files.contains(&PathBuf::from("test_file_2.txt")));
assert!(files.contains(&PathBuf::from("test_file_3.txt")));
}
#[test]
fn test_create_symlinks() {
let tempdir = tempfile::tempdir().expect("Error creating temp directory");
println!("Running test in '{:?}'", tempdir);
let temp_path = tempdir.path().to_string_lossy().to_string().clone();
let file = PathBuf::from(format!("{}/test_file", temp_path));
fs::File::create(&file).expect("Error creating mock file...");
let symlink = PathBuf::from(format!("{}/test_link", temp_path));
create_symlink(&file, &symlink).expect("Error creating symlink.");
let broken_symlink = PathBuf::from(format!("{}/test_broken_link", temp_path));
create_symlink(&PathBuf::from("broken_link"), &broken_symlink)
.expect("Error creating broken symlink.");
assert!(file.symlink_metadata().is_ok());
assert!(symlink.symlink_metadata().is_ok());
assert!(broken_symlink.symlink_metadata().is_ok());
}
#[test]
fn test_same_file() {
let tempdir = tempfile::tempdir().expect("Error creating temp directory");
println!("Running test in '{:?}'", tempdir);
let temp_path = tempdir.path().to_str().unwrap();
let mock_files: PathList = vec![
[temp_path, "test_FILE"].iter().collect(),
[temp_path, "test_File"].iter().collect(),
[temp_path, "test_file"].iter().collect(),
];
let other_file = PathBuf::from(format!("{}/other_file", temp_path));
for file in &mock_files {
fs::File::create(file)
.expect("Error creating mock file...")
.write_all(b"Hello, world!")
.expect("Error writting in the mock file...");
}
fs::File::create(&other_file)
.expect("Error creating mock file...")
.write_all(b"Hello, world!")
.expect("Error writting in the mock file...");
#[cfg(any(windows, target_os = "macos"))]
{
assert!(is_same_file(&mock_files[0], &mock_files[0]));
assert!(is_same_file(&mock_files[0], &mock_files[1]));
assert!(is_same_file(&mock_files[0], &mock_files[2]));
assert!(is_same_file(&mock_files[1], &mock_files[2]));
assert!(!is_same_file(&mock_files[0], &other_file));
}
#[cfg(not(any(windows, target_os = "macos")))]
{
assert!(is_same_file(&mock_files[0], &mock_files[0]));
assert!(!is_same_file(&mock_files[0], &mock_files[1]));
assert!(!is_same_file(&mock_files[0], &mock_files[2]));
assert!(!is_same_file(&mock_files[1], &mock_files[2]));
assert!(!is_same_file(&mock_files[0], &other_file));
}
}
fn generate_recursive_tempdir() -> (tempfile::TempDir, String) {
let tempdir = tempfile::tempdir().expect("Error creating temp directory");
println!("Running test in '{:?}'", tempdir);
let temp_path = tempdir.path().to_string_lossy().to_string().clone();
#[rustfmt::skip]
let mock_dirs: PathList = vec![
[&temp_path, ".hidden_mock_dir"].iter().collect(),
[&temp_path, "mock_dir_1"].iter().collect(),
[&temp_path, "mock_dir_1", "mock_dir_2"].iter().collect(),
[&temp_path, "mock_dir_1", "mock_dir_2", "mock_dir_3"].iter().collect(),
];
#[rustfmt::skip]
let mock_files: PathList = vec![
[&temp_path, "test_file.txt"].iter().collect(),
[&temp_path, ".hidden_test_file.txt"].iter().collect(),
[&mock_dirs[0], &PathBuf::from("test_file.txt")].iter().collect(),
[&mock_dirs[1], &PathBuf::from("test_file.txt")].iter().collect(),
[&mock_dirs[2], &PathBuf::from("test_file.txt")].iter().collect(),
[&mock_dirs[3], &PathBuf::from("test_file.txt")].iter().collect(),
];
for mock_dir in &mock_dirs {
fs::create_dir(mock_dir).expect("Error creating mock directory...");
}
for file in &mock_files {
fs::File::create(file).expect("Error creating mock file...");
}
(tempdir, temp_path)
}
#[test]
fn get_paths_recursive() {
let (_tempdir, temp_path) = generate_recursive_tempdir();
let mode = RunMode::Recursive {
paths: vec![temp_path.clone()],
max_depth: None,
hidden: false,
};
let files = get_paths(&mode);
#[rustfmt::skip]
let listed_files: PathList = vec![
[&temp_path, "test_file.txt"].iter().collect(),
[&temp_path, "mock_dir_1", "test_file.txt"].iter().collect(),
[&temp_path, "mock_dir_1", "mock_dir_2", "test_file.txt"].iter().collect(),
[&temp_path, "mock_dir_1", "mock_dir_2", "mock_dir_3", "test_file.txt"]
.iter().collect(),
];
for file in &listed_files {
assert!(files.contains(file));
}
#[rustfmt::skip]
let non_listed_files: PathList = vec![
[&temp_path, ".hidden_test_file.txt"].iter().collect(),
[&temp_path, ".hidden_mock_dir", "test_file.txt"].iter().collect(),
];
for file in &non_listed_files {
assert!(!files.contains(file));
}
}
#[test]
fn get_paths_recursive_depth() {
let (_tempdir, temp_path) = generate_recursive_tempdir();
let mode = RunMode::Recursive {
paths: vec![temp_path.clone()],
max_depth: Some(2),
hidden: false,
};
let files = get_paths(&mode);
let listed_files: PathList = vec![
[&temp_path, "test_file.txt"].iter().collect(),
[&temp_path, "mock_dir_1", "test_file.txt"].iter().collect(),
];
for file in &listed_files {
assert!(files.contains(file));
}
#[rustfmt::skip]
let non_listed_files: PathList = vec![
[&temp_path, "mock_dir_1", "mock_dir_2", "test_file.txt"].iter().collect(),
[&temp_path, "mock_dir_1", "mock_dir_2", "mock_dir_3", "test_file.txt"]
.iter().collect(),
[&temp_path, ".hidden_test_file.txt"].iter().collect(),
[&temp_path, ".hidden_mock_dir", "test_file.txt"].iter().collect(),
];
for file in &non_listed_files {
assert!(!files.contains(file));
}
}
#[test]
fn get_paths_recursive_hidden() {
let (_tempdir, temp_path) = generate_recursive_tempdir();
let mode = RunMode::Recursive {
paths: vec![temp_path.clone()],
max_depth: None,
hidden: true,
};
let files = get_paths(&mode);
#[rustfmt::skip]
let listed_files: PathList = vec![
[&temp_path, "test_file.txt"].iter().collect(),
[&temp_path, "mock_dir_1", "test_file.txt"].iter().collect(),
[&temp_path, "mock_dir_1", "mock_dir_2", "test_file.txt"].iter().collect(),
[&temp_path, "mock_dir_1", "mock_dir_2", "mock_dir_3", "test_file.txt"]
.iter().collect(),
[&temp_path, ".hidden_test_file.txt"].iter().collect(),
[&temp_path, ".hidden_mock_dir", "test_file.txt"].iter().collect(),
];
for file in &listed_files {
assert!(files.contains(file));
}
}
#[test]
fn cleanup() {
let tempdir = tempfile::tempdir().expect("Error creating temp directory");
println!("Running test in '{:?}'", tempdir);
let temp_path = tempdir.path().to_str().unwrap();
let mock_dirs: PathList = vec![
[temp_path, "mock_dir_1"].iter().collect(),
[temp_path, "mock_dir_1", "mock_dir_2"].iter().collect(),
];
#[rustfmt::skip]
let mock_files: PathList = vec![
[temp_path, "test_file.txt"].iter().collect(),
[&mock_dirs[0], &PathBuf::from("test_file.txt")].iter().collect(),
[&mock_dirs[1], &PathBuf::from("test_file.txt")].iter().collect(),
];
for mock_dir in &mock_dirs {
fs::create_dir(mock_dir).expect("Error creating mock directory...");
}
for file in &mock_files {
fs::File::create(file).expect("Error creating mock file...");
}
let symlink: PathBuf = [temp_path, "test_link"].iter().collect();
let broken_symlink: PathBuf = [temp_path, "test_broken_link"].iter().collect();
create_symlink(&mock_files[0], &symlink).expect("Error creating symlink.");
create_symlink(&PathBuf::from("broken_link"), &broken_symlink)
.expect("Error creating broken symlink.");
let mut mock_paths = PathList::new();
mock_paths.append(&mut mock_files.clone());
mock_paths.push(symlink.clone());
mock_paths.push(broken_symlink.clone());
mock_paths.append(&mut mock_dirs.clone());
#[rustfmt::skip]
let false_files: PathList = vec![
[temp_path, "false_file.txt"].iter().collect(),
[&mock_dirs[0], &PathBuf::from("false_file.txt")].iter().collect(),
[&mock_dirs[1], &PathBuf::from("false_file.txt")].iter().collect(),
];
mock_paths.append(&mut mock_files.clone());
mock_paths.append(&mut mock_files.clone());
mock_paths.append(&mut mock_files.clone());
mock_paths.append(&mut mock_files.clone());
let mock_paths = cleanup_paths(mock_paths, false);
let mut listed_files = PathList::new();
listed_files.append(&mut mock_files.clone());
listed_files.push(symlink.clone());
listed_files.push(broken_symlink.clone());
for file in &listed_files {
assert!(mock_paths.contains(file));
assert_eq!(mock_paths.iter().filter(|f| f == &file).count(), 1);
}
#[rustfmt::skip]
let mut non_listed_files = PathList::new();
non_listed_files.append(&mut mock_dirs.clone());
non_listed_files.append(&mut false_files.clone());
for file in &non_listed_files {
assert!(!mock_paths.contains(file));
}
}
}