use std::fs;
use std::path::{Path, PathBuf};
use anyhow::Context as _;
pub(super) struct Scratch {
pub(super) path: PathBuf,
}
impl Scratch {
pub(super) fn new(path: PathBuf) -> Self {
Self { path }
}
}
impl Drop for Scratch {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.path);
}
}
pub(super) fn remove_dir_if_exists(dir: &Path) {
let _ = fs::remove_dir_all(dir);
}
#[cfg(unix)]
pub(super) fn replace(exe: &Path, staged: &Path) -> anyhow::Result<()> {
fs::rename(staged, exe).with_context(|| format!("cannot replace {}", exe.display()))
}
#[cfg(windows)]
pub(super) fn replace(exe: &Path, staged: &Path) -> anyhow::Result<()> {
let old = append_to_name(exe, ".old");
let _ = fs::remove_file(&old);
fs::rename(exe, &old).with_context(|| {
format!(
"cannot move the running binary aside to {} -- is that directory writable by you?",
old.display()
)
})?;
if let Err(error) = fs::rename(staged, exe) {
let _ = fs::rename(&old, exe);
let _ = fs::remove_file(staged);
return Err(error)
.with_context(|| format!("cannot install the new binary at {}", exe.display()));
}
let _ = fs::remove_file(&old);
Ok(())
}
#[cfg(windows)]
pub fn cleanup_stale_old() {
if let Ok(exe) = std::env::current_exe() {
let _ = fs::remove_file(append_to_name(&exe, ".old"));
}
}
#[cfg(not(windows))]
pub fn cleanup_stale_old() {}
pub(super) fn append_to_name(path: &Path, suffix: &str) -> PathBuf {
let mut name = path.as_os_str().to_os_string();
name.push(suffix);
PathBuf::from(name)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn replacing_puts_the_new_binary_in_place_of_the_old() {
let tmp = tempfile::tempdir().unwrap();
let exe = tmp.path().join("pmpx");
let staged = append_to_name(&exe, ".new");
std::fs::write(&exe, b"old").unwrap();
std::fs::write(&staged, b"new").unwrap();
replace(&exe, &staged).expect("should replace");
assert_eq!(std::fs::read(&exe).unwrap(), b"new");
assert!(!staged.exists(), "the staged file should have been moved");
}
#[test]
fn the_backup_name_keeps_the_extension() {
assert_eq!(
append_to_name(Path::new("/usr/local/bin/pmpx"), ".new"),
PathBuf::from("/usr/local/bin/pmpx.new")
);
assert_eq!(
append_to_name(Path::new(r"C:\bin\pmpx.exe"), ".old"),
PathBuf::from(r"C:\bin\pmpx.exe.old")
);
}
}