use std::fs;
use std::path::{Path, PathBuf};
use std::process;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use deezconfigs::{ui, walk};
use super::common::{
determine_config_root, get_config_root_from_git, get_home_directory, get_hooks_for_command,
is_git_remote_uri, run_hooks,
};
#[allow(clippy::too_many_lines)] pub fn sync(root: Option<&String>, verbose: bool, pull_before_sync: bool) -> Result<(), i32> {
let root = if pull_before_sync {
if is_git_remote_uri(root) {
eprintln!(
"{fatal}: '--pull' only works with local config roots.",
fatal = ui::Color::error("fatal")
);
return Err(2);
}
let root = determine_config_root(root, true)?;
run_git_pull_in_root(&root)?;
root
} else if is_git_remote_uri(root) {
get_config_root_from_git(root.expect("not empty, contains a `git:` prefix"), verbose)?
} else {
determine_config_root(root, true)?
};
let home = get_home_directory()?;
let hooks = get_hooks_for_command(&root, &home, verbose)?;
let mut nb_hooks_ran = 0;
nb_hooks_ran += run_hooks(|| hooks.pre_sync())?;
let files = Arc::new(Mutex::new(Vec::with_capacity(20)));
let nb_files_synced = AtomicUsize::new(0);
let nb_errors = AtomicUsize::new(0);
walk::find_files_recursively(&root, |p| {
debug_assert!(!p.is_dir());
let source = root.join(p);
let destination = home.join(p);
if destination.is_dir() {
if let Err(err) = fs::remove_dir(&destination) {
nb_errors.fetch_add(1, Ordering::Relaxed);
eprintln!(
"{error}: Could not remove exising directory '{}': {err}",
destination.display(),
error = ui::Color::error("error"),
);
return;
}
}
if let Err(err) = fs::create_dir_all(
destination
.parent()
.expect("at the bare minimum, `parent` is `$HOME`"),
) {
nb_errors.fetch_add(1, Ordering::Relaxed);
eprintln!(
"{error}: Could not copy '{}' to Home: {err}",
p.display(),
error = ui::Color::error("error"),
);
return;
}
if source.is_symlink() {
if destination.is_file() {
if let Err(err) = fs::remove_file(&destination) {
nb_errors.fetch_add(1, Ordering::Relaxed);
eprintln!(
"{error}: Could not remove exising file '{}': {err}",
destination.display(),
error = ui::Color::error("error"),
);
return;
}
}
let target: PathBuf = match fs::read_link(&source) {
Ok(target) => target,
Err(err) => {
nb_errors.fetch_add(1, Ordering::Relaxed);
eprintln!(
"{error}: Could not read symlink '{}': {err}",
p.display(),
error = ui::Color::error("error"),
);
return;
}
};
#[cfg(unix)]
let res = std::os::unix::fs::symlink(&target, &destination);
#[cfg(windows)]
let res = std::os::windows::fs::symlink_file(&target, &destination);
if let Err(err) = res {
nb_errors.fetch_add(1, Ordering::Relaxed);
eprintln!(
"{error}: Could not create symlink '{}': {err}",
p.display(),
error = ui::Color::error("error"),
);
return;
}
} else {
if destination.is_symlink()
&& let Err(err) = fs::remove_file(&destination)
{
nb_errors.fetch_add(1, Ordering::Relaxed);
eprintln!(
"{error}: Could not remove exising symlink '{}': {err}",
destination.display(),
error = ui::Color::error("error"),
);
return;
}
if let Err(err) = fs::copy(source, destination) {
nb_errors.fetch_add(1, Ordering::Relaxed);
eprintln!(
"{error}: Could not copy '{}' to Home: {err}",
p.display(),
error = ui::Color::error("error"),
);
return;
}
}
if verbose {
let file = p.to_string_lossy().to_string();
if let Ok(mut files) = files.lock() {
files.push(file);
drop(files);
} else {
println!("{}", p.display());
}
}
nb_files_synced.fetch_add(1, Ordering::Relaxed);
});
let mut files = Arc::try_unwrap(files)
.expect("processing is over, we're back to a single thread.")
.into_inner()
.unwrap();
files.sort();
ui::print_files(&files);
nb_hooks_ran += run_hooks(|| hooks.post_sync())?;
let nb_files_synced = nb_files_synced.into_inner();
let nb_errors = nb_errors.into_inner();
ui::print_summary(
ui::Action::Sync,
&root,
nb_files_synced,
nb_errors,
nb_hooks_ran,
);
if nb_errors > 0 { Err(1) } else { Ok(()) }
}
fn run_git_pull_in_root(root: &Path) -> Result<(), i32> {
let status = process::Command::new("git")
.current_dir(root)
.arg("pull")
.status();
match status {
Ok(status) => match status.code() {
Some(0) => Ok(()),
Some(code) => Err(code),
None => Err(1),
},
Err(err) => {
eprintln!(
"{fatal}: Could not run 'git pull': {err}",
fatal = ui::Color::error("fatal")
);
Err(1)
}
}
}