Handle collisions with existing configs
+ Backup all conflicts to the backup directory set through CLI + Resolves any symlinks to backup their destination files or paths
This commit is contained in:
parent
34ff8f54ab
commit
fded317a65
69
src/kot.rs
69
src/kot.rs
|
@ -6,6 +6,9 @@
|
||||||
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
|
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
|
||||||
##############################################################################*/
|
##############################################################################*/
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use crate::kot::fs::check_collisions;
|
||||||
|
|
||||||
pub mod cli;
|
pub mod cli;
|
||||||
pub mod fs;
|
pub mod fs;
|
||||||
pub mod io;
|
pub mod io;
|
||||||
|
@ -18,15 +21,18 @@ pub mod io;
|
||||||
|
|
||||||
// Creates symbolic links to the configurations we're installing
|
// Creates symbolic links to the configurations we're installing
|
||||||
// TODO: On error, revert to last good state
|
// TODO: On error, revert to last good state
|
||||||
|
// TODO: User script to execute after installing configs successfully
|
||||||
|
// TODO: Function to uninstall configs. Loop through dotfiles and restore backup files or delete configs
|
||||||
pub fn install_configs(args: & cli::Cli) -> std::io::Result<()> {
|
pub fn install_configs(args: & cli::Cli) -> std::io::Result<()> {
|
||||||
// Get the configurations and their target installation paths
|
// Get the configurations and their target installation paths
|
||||||
// + Checks for conflicts and prompts user to abort or continue
|
// + Checks for conflicts and prompts user to abort or continue
|
||||||
let config_map = fs::get_target_paths(&args)?;
|
let config_map = fs::get_target_paths(&args)?;
|
||||||
|
handle_collisions(&args, &config_map)?;
|
||||||
|
|
||||||
// At this point there are either no conflicts or the user agreed to them
|
// At this point there are either no conflicts or the user agreed to them
|
||||||
|
println!("Installing configs:");
|
||||||
for (config_path, target_path) in &config_map {
|
for (config_path, target_path) in &config_map {
|
||||||
println!("Installing config: {:?}\n+ At location: {:?}\n", config_path, target_path);
|
println!(" + {:?}", target_path);
|
||||||
|
|
||||||
match std::os::unix::fs::symlink(config_path, target_path) {
|
match std::os::unix::fs::symlink(config_path, target_path) {
|
||||||
Ok(()) => (), // Configuration installed successfully
|
Ok(()) => (), // Configuration installed successfully
|
||||||
Err(_e) => {
|
Err(_e) => {
|
||||||
|
@ -45,3 +51,62 @@ pub fn install_configs(args: & cli::Cli) -> std::io::Result<()> {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_collisions(args : & cli::Cli,
|
||||||
|
config_map : & fs::HashMap<PathBuf, PathBuf>) -> io::Result<()> {
|
||||||
|
let conflicts = check_collisions(&config_map)
|
||||||
|
.expect("Error: Failed to check collisions");
|
||||||
|
|
||||||
|
// If we found collisions in the configurations
|
||||||
|
if &conflicts.len() > &0 {
|
||||||
|
// 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
|
||||||
|
match io::prompt(msg) {
|
||||||
|
true => return Err(std::io::Error::from(std::io::ErrorKind::AlreadyExists)),
|
||||||
|
false => {
|
||||||
|
// Backup each conflicting config at the install location
|
||||||
|
for backup_target in conflicts.iter() {
|
||||||
|
backup_config(backup_target, &args.backup_dir)
|
||||||
|
.expect(format!("Error: Unable to backup config: {:?}", backup_target)
|
||||||
|
.as_str())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a backup of configurations that conflict
|
||||||
|
// + Backup directory location is specified by CLI --backup-dir
|
||||||
|
// TODO: Automatically create backup directory
|
||||||
|
// 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?
|
||||||
|
fn backup_config(config_path: & fs::PathBuf, backup_dir: & fs::PathBuf) -> io::Result<()> {
|
||||||
|
let mut backup_path = backup_dir.to_owned();
|
||||||
|
backup_path.push(config_path.file_name().unwrap());
|
||||||
|
match config_path.is_dir() {
|
||||||
|
true => {
|
||||||
|
// Copy directory with recursion using fs_extra::dir::move_dir
|
||||||
|
let mut options = fs::dir::CopyOptions::new();
|
||||||
|
options.copy_inside = true;
|
||||||
|
// TODO: Add a flag to overwrite backups, otherwise warn and abort
|
||||||
|
options.overwrite = true;
|
||||||
|
fs::dir::move_dir(config_path, backup_path, &options)
|
||||||
|
},
|
||||||
|
false => {
|
||||||
|
// Copy single configuration file
|
||||||
|
let mut options = fs_extra::file::CopyOptions::new();
|
||||||
|
options.overwrite = true;
|
||||||
|
fs_extra::file::move_file(config_path, backup_path, &options)
|
||||||
|
},
|
||||||
|
}.expect(&format!("Error: Unable to backup config: {:?}", config_path));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -9,9 +9,9 @@
|
||||||
// Allow the use of kot::fs::Path and kot::fs::PathBuf from std::path::
|
// Allow the use of kot::fs::Path and kot::fs::PathBuf from std::path::
|
||||||
pub use std::path::{Path, PathBuf};
|
pub use std::path::{Path, PathBuf};
|
||||||
pub use std::collections::HashMap;
|
pub use std::collections::HashMap;
|
||||||
|
pub use fs_extra::dir;
|
||||||
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use fs_extra::dir;
|
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// IMPLEMENTATION
|
// IMPLEMENTATION
|
||||||
|
@ -19,32 +19,9 @@ use fs_extra::dir;
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
// Creates a backup of configurations that conflict
|
|
||||||
// + Backup directory location is specified by CLI --backup-dir
|
|
||||||
// TODO: Automatically create backup directory
|
|
||||||
// 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?
|
|
||||||
fn backup_config(config_path: & PathBuf, backup_dir: & PathBuf) -> super::io::Result<()> {
|
|
||||||
let mut backup_path = backup_dir.to_owned();
|
|
||||||
backup_path.push(config_path.file_name().unwrap());
|
|
||||||
match config_path.is_dir() {
|
|
||||||
true => {
|
|
||||||
// Copy directory with recursion using fs_extra::dir::move_dir
|
|
||||||
let mut options = dir::CopyOptions::new();
|
|
||||||
options.copy_inside = true;
|
|
||||||
dir::move_dir(config_path, backup_path, &options)
|
|
||||||
},
|
|
||||||
false => {
|
|
||||||
// Copy single configuration file
|
|
||||||
let options = fs_extra::file::CopyOptions::new();
|
|
||||||
fs_extra::file::move_file(config_path, backup_path, &options)
|
|
||||||
},
|
|
||||||
}.expect(&format!("Error: Unable to backup config: {:?}", config_path));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize and return a HashMap<config_dir, config_install_location>
|
// Initialize and return a HashMap<config_dir, config_install_location>
|
||||||
// Later used to check each install location for conflicts before installing
|
// Later used to check each install location for conflicts before installing
|
||||||
|
// This function does not create or modify any files or directories
|
||||||
pub fn get_target_paths(args: & super::cli::Cli) -> super::io::Result<HashMap<PathBuf, PathBuf>> {
|
pub fn get_target_paths(args: & super::cli::Cli) -> super::io::Result<HashMap<PathBuf, PathBuf>> {
|
||||||
let mut config_map = HashMap::new();
|
let mut config_map = HashMap::new();
|
||||||
|
|
||||||
|
@ -59,20 +36,9 @@ pub fn get_target_paths(args: & super::cli::Cli) -> super::io::Result<HashMap<Pa
|
||||||
// Create full path to target config file (or directory) by push onto install path
|
// Create full path to target config file (or directory) by push onto install path
|
||||||
config_target.push(entry.file_name());
|
config_target.push(entry.file_name());
|
||||||
|
|
||||||
// If the target configuration file or directory already exists
|
|
||||||
if config_target.exists() {
|
|
||||||
// Ask client if they would like to abort given the config collision
|
|
||||||
let msg = format!("Configuration already exists: {:?}\
|
|
||||||
\nAbort? Enter y/n or Y/N: ", config_target);
|
|
||||||
|
|
||||||
// If we abort, exit; If we continue, back up the configs
|
|
||||||
match super::io::prompt(msg) {
|
|
||||||
true => return Err(std::io::Error::from(std::io::ErrorKind::AlreadyExists)),
|
|
||||||
false => backup_config(&config_target, &args.backup_dir).ok(),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// If the entry doesn't already exist, insert it into the config_map
|
// If the entry doesn't already exist, insert it into the config_map
|
||||||
|
// Key is full path to source config from dotfiles repo we're installing
|
||||||
|
// Value is desired full path to config at final install location
|
||||||
// TODO: If the entry does exist, should there be an exception?
|
// TODO: If the entry does exist, should there be an exception?
|
||||||
config_map.entry(entry.path().to_owned())
|
config_map.entry(entry.path().to_owned())
|
||||||
.or_insert(config_target.to_owned());
|
.or_insert(config_target.to_owned());
|
||||||
|
@ -81,8 +47,18 @@ pub fn get_target_paths(args: & super::cli::Cli) -> super::io::Result<HashMap<Pa
|
||||||
config_target.pop();
|
config_target.pop();
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(config_map)
|
Ok(config_map)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn check_collisions(config_map : & HashMap<PathBuf, PathBuf>) -> super::io::Result<Vec<PathBuf>> {
|
||||||
|
let mut config_conflicts = vec![];
|
||||||
|
for (_path, target_config) in config_map.iter() {
|
||||||
|
// If the target configuration file or directory already exists
|
||||||
|
if target_config.exists() {
|
||||||
|
config_conflicts.push(target_config.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(config_conflicts)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue