2021-09-28 22:54:37 +00:00
|
|
|
/*##############################################################################
|
|
|
|
## Author: Shaun Reed ##
|
|
|
|
## Legal: All Content (c) 2021 Shaun Reed, all rights reserved ##
|
|
|
|
## About: Root module for Linux configuration manager kot ##
|
|
|
|
## ##
|
|
|
|
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
|
|
|
|
##############################################################################*/
|
|
|
|
|
2021-12-27 13:31:56 +00:00
|
|
|
use std::path::PathBuf;
|
|
|
|
use crate::kot::fs::check_collisions;
|
|
|
|
|
2021-09-28 22:54:37 +00:00
|
|
|
pub mod cli;
|
|
|
|
pub mod fs;
|
|
|
|
pub mod io;
|
|
|
|
|
2021-12-29 18:31:09 +00:00
|
|
|
/// Result alias to return result with Error of various types
|
|
|
|
pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
|
|
|
|
2021-09-28 22:54:37 +00:00
|
|
|
// =============================================================================
|
|
|
|
// IMPLEMENTATION
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
2021-12-29 18:31:09 +00:00
|
|
|
/// Creates symbolic links to the configurations we're installing
|
2021-12-27 00:08:58 +00:00
|
|
|
// TODO: On error, revert to last good state
|
2021-12-27 13:31:56 +00:00
|
|
|
// TODO: User script to execute after installing configs successfully
|
2021-12-29 18:31:09 +00:00
|
|
|
pub fn install_configs(args: & cli::Cli) -> Result<()> {
|
2021-09-28 22:54:37 +00:00
|
|
|
// Get the configurations and their target installation paths
|
|
|
|
// + Checks for conflicts and prompts user to abort or continue
|
|
|
|
let config_map = fs::get_target_paths(&args)?;
|
2021-12-29 18:31:09 +00:00
|
|
|
|
|
|
|
// Check if there are any existing files in the install directory that are also within the dotfiles to install
|
2021-12-27 13:31:56 +00:00
|
|
|
handle_collisions(&args, &config_map)?;
|
2021-09-28 22:54:37 +00:00
|
|
|
|
|
|
|
// At this point there are either no conflicts or the user agreed to them
|
2021-12-27 13:31:56 +00:00
|
|
|
println!("Installing configs:");
|
2021-09-28 22:54:37 +00:00
|
|
|
for (config_path, target_path) in &config_map {
|
2021-12-27 13:31:56 +00:00
|
|
|
println!(" + {:?}", target_path);
|
2021-09-28 22:54:37 +00:00
|
|
|
match std::os::unix::fs::symlink(config_path, target_path) {
|
2021-12-27 00:08:58 +00:00
|
|
|
Ok(()) => (), // Configuration installed successfully
|
2021-09-28 22:54:37 +00:00
|
|
|
Err(_e) => {
|
2021-12-29 18:31:09 +00:00
|
|
|
// Attempt to remove the file or directory first, and then symlink the new config
|
2021-09-28 22:54:37 +00:00
|
|
|
match target_path.is_dir() {
|
2021-12-27 00:08:58 +00:00
|
|
|
true => fs_extra::dir::remove(target_path)
|
|
|
|
.expect(&format!("Error: Unable to remove directory: {:?}", target_path)),
|
|
|
|
false => fs_extra::file::remove(target_path)
|
|
|
|
.expect(&format!("Error: Unable to remove file: {:?}", target_path)),
|
2021-09-28 22:54:37 +00:00
|
|
|
};
|
2021-12-27 00:08:58 +00:00
|
|
|
// Try to symlink the config again, if failure exit with error
|
2021-12-29 18:31:09 +00:00
|
|
|
std::os::unix::fs::symlink(config_path, target_path)?;
|
2021-09-28 22:54:37 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2021-12-29 18:31:09 +00:00
|
|
|
|
2021-09-28 22:54:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
2021-12-27 13:31:56 +00:00
|
|
|
|
2021-12-29 18:31:09 +00:00
|
|
|
/// Handles collisions between existing files and dotfiles we're installing
|
2021-12-27 13:31:56 +00:00
|
|
|
fn handle_collisions(args : & cli::Cli,
|
2021-12-29 18:31:09 +00:00
|
|
|
config_map : & fs::HashMap<PathBuf, PathBuf>) -> Result<()> {
|
|
|
|
// Check if we found any collisions in the configurations
|
|
|
|
match check_collisions(&config_map) {
|
|
|
|
None => {
|
|
|
|
return Ok(()) // There were no collisions, configurations pass pre-install checks
|
|
|
|
},
|
|
|
|
Some(conflicts) => {
|
|
|
|
// Ask client if they would like to abort given the config collisions
|
|
|
|
let mut msg = format!("The following configurations already exist:");
|
|
|
|
for config in conflicts.iter() {
|
|
|
|
msg += format!("\n {:?}", config).as_str();
|
|
|
|
}
|
|
|
|
msg += format!("\nIf you continue, backups will be made in {:?}. \
|
|
|
|
Any configurations there will be overwritten.\
|
|
|
|
\nAbort? Enter y/n or Y/N: ", &args.backup_dir).as_str();
|
|
|
|
|
|
|
|
// If we abort, exit; If we continue, back up the configs
|
|
|
|
// TODO: Group this in with the --force flag?; Or make a new --adopt flag?
|
|
|
|
match io::prompt(msg) {
|
|
|
|
true => return Ok(()),
|
|
|
|
false => {
|
|
|
|
// Backup each conflicting config at the install location
|
|
|
|
for backup_target in conflicts.iter() {
|
|
|
|
backup_config(backup_target, &args)?;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
},
|
2021-12-27 13:31:56 +00:00
|
|
|
};
|
2021-12-29 18:31:09 +00:00
|
|
|
|
2021-12-27 13:31:56 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Creates a backup of configurations that conflict
|
|
|
|
// + Backup directory location is specified by CLI --backup-dir
|
|
|
|
// TODO: .kotignore in dotfiles repo to specify files to not install / backup
|
|
|
|
// TODO: .kotrc in dotfiles repo or home dir to set backup-dir and install-dir?
|
2021-12-29 18:31:09 +00:00
|
|
|
fn backup_config(config_path: & fs::PathBuf, args: & cli::Cli) -> Result<()> {
|
|
|
|
let mut backup_path = args.backup_dir.to_owned();
|
2021-12-27 13:31:56 +00:00
|
|
|
backup_path.push(config_path.file_name().unwrap());
|
2021-12-29 18:31:09 +00:00
|
|
|
|
|
|
|
// Check if the configuration we're backing up is a directory or a single file
|
2021-12-27 13:31:56 +00:00
|
|
|
match config_path.is_dir() {
|
|
|
|
true => {
|
2021-12-29 18:31:09 +00:00
|
|
|
// Copy directory with recursion using move_dir() wrapper function
|
2021-12-27 13:31:56 +00:00
|
|
|
let mut options = fs::dir::CopyOptions::new();
|
|
|
|
options.copy_inside = true;
|
2021-12-29 18:31:09 +00:00
|
|
|
options.overwrite = args.force;
|
|
|
|
if let Err(e) = fs::move_dir(config_path, &backup_path, Some(&options))
|
|
|
|
.map_err(|e| e.into()) {
|
|
|
|
return Err(e)
|
|
|
|
}
|
2021-12-27 13:31:56 +00:00
|
|
|
},
|
|
|
|
false => {
|
|
|
|
// Copy single configuration file
|
|
|
|
let mut options = fs_extra::file::CopyOptions::new();
|
2021-12-29 18:31:09 +00:00
|
|
|
options.overwrite = args.force;
|
|
|
|
if let Err(e) = fs::move_file(config_path, &backup_path, Some(&options))
|
|
|
|
.map_err(|e| e.into()) {
|
|
|
|
return Err(e)
|
|
|
|
}
|
2021-12-27 13:31:56 +00:00
|
|
|
},
|
2021-12-29 18:31:09 +00:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Function to uninstall configs.
|
|
|
|
// + Loops through dotfiles and restore backup files or delete configs
|
|
|
|
fn _uninstall_configs() -> Result<()> {
|
2021-12-27 13:31:56 +00:00
|
|
|
Ok(())
|
|
|
|
}
|