use std::{os::linux::fs::MetadataExt, path::Path};
#[derive(Debug, clap::Args)]
pub(crate) struct InstallCmd {
#[arg(short, long)]
dry_run: bool,
#[arg(short, long)]
force: bool,
#[arg(short, long)]
ignore_missing: bool,
key: Option<String>,
#[command(subcommand)]
cmd: Option<Commands>,
}
#[derive(Debug, clap::Subcommand)]
enum Commands {
Symlink(Symlink),
}
impl super::Command for InstallCmd {
fn run(
&self,
_: &super::nedots::RootCmd,
config: &crate::config::Config,
) -> Result<(), super::CommandError> {
let pb = indicatif::ProgressBar::new_spinner();
if let Some(key) = &self.key {
match config.get_sources_as_hashmap().get(key.as_str()) {
Some(val) => {
super::backup::backup(
&[val.to_path_buf()],
&config.root.join(super::backup::get_dst()),
true,
)?;
let dst = Path::new(val);
let src = config.get_dst_from_src(dst)?;
pb.tick();
pb.set_message(format!("Installing {}...", val.to_string_lossy()));
install(&src, dst, self.dry_run, self.force, self.ignore_missing)?;
}
None => {
log::error!("{} not found!", key);
}
}
} else {
super::backup::backup(
&config.sources,
&config.root.join(super::backup::get_dst()),
true,
)?;
for source in &config.sources {
let dst = Path::new(source);
let src = config.get_dst_from_src(dst)?;
pb.tick();
pb.set_message(format!("Installing {}...", source.to_string_lossy()));
install(&src, dst, self.dry_run, self.force, self.ignore_missing)?;
}
}
pb.finish_and_clear();
Ok(())
}
}
fn install(
src: &Path,
dst: &Path,
dry_run: bool,
force: bool,
ignore_missing: bool,
) -> Result<(), super::CommandError> {
log::debug!(
"Installing {} -> {}",
src.file_name().unwrap().to_string_lossy(),
dst.to_string_lossy()
);
if src.is_dir() {
for entry in src.read_dir()? {
let path = entry?.path();
install(
&path,
&dst.join(&path.file_name().unwrap()),
dry_run,
force,
ignore_missing,
)?;
}
} else {
let src_metadata: std::fs::Metadata;
let src_modified: std::time::SystemTime;
match src.metadata() {
Ok(res) => {
src_modified = res.modified()?;
src_metadata = res;
}
Err(err) => {
log::error!(
"{} does not exist - check your config for old entries",
src.to_string_lossy().to_string()
);
return Err(crate::cmd::CommandError::IoError(err));
}
};
let mut dst_metadata: Option<std::fs::Metadata> = None;
let dst_modified: std::time::SystemTime;
match dst.metadata() {
Ok(res) => {
dst_modified = res.modified()?;
dst_metadata = Some(res);
}
Err(_) => match ignore_missing {
true => dst_modified = std::time::SystemTime::now(),
false => dst_modified = std::time::SystemTime::UNIX_EPOCH,
},
};
if !force && dst_modified.gt(&src_modified) {
log::error!(
"Desination modified sooner than source, {} seconds difference ({})",
(src_modified.elapsed()? - dst_modified.elapsed()?).as_secs(),
dst.to_string_lossy().to_string()
);
return Ok(());
}
if let Some(dst_metadata) = dst_metadata {
if dst_metadata.st_uid().ne(&src_metadata.st_uid()) {
log::warn!(
"Insufficient permissions ({})",
dst.to_string_lossy().to_string(),
);
return Ok(());
}
if dst_metadata.len().ne(&src_metadata.len()) {
log::info!(
"{} bytes difference ({})",
dst_metadata.len().abs_diff(src_metadata.len()),
dst.to_string_lossy().to_string()
);
}
}
if dry_run {
return Ok(());
} else {
super::gather::gather(src, dst)?;
}
}
Ok(())
}
#[derive(Debug, clap::Args)]
struct Symlink;
impl super::Command for Symlink {
fn run(
&self,
_: &super::nedots::RootCmd,
_: &crate::config::Config,
) -> Result<(), super::CommandError> {
Ok(())
}
}