diff --git a/install/src/command.rs b/install/src/command.rs index ef749e72d7..52f2c0b22b 100644 --- a/install/src/command.rs +++ b/install/src/command.rs @@ -39,6 +39,7 @@ static BULLET: Emoji = Emoji("• ", "* "); static SPARKLE: Emoji = Emoji("✨ ", ""); static PACKAGE: Emoji = Emoji("📦 ", ""); static INFORMATION: Emoji = Emoji("ℹ️ ", ""); +static RECYCLING: Emoji = Emoji("♻️ ", ""); /// Creates a new process bar for processing that will take an unknown amount of time fn new_spinner_progress_bar() -> ProgressBar { @@ -786,6 +787,61 @@ fn symlink_dir, Q: AsRef>(src: P, dst: Q) -> std::io::Resul std::os::unix::fs::symlink(src, dst) } +pub fn gc(config_file: &str) -> Result<(), String> { + let config = Config::load(config_file)?; + + let entries = fs::read_dir(&config.releases_dir) + .map_err(|err| format!("Unable to read {}: {}", config.releases_dir.display(), err))?; + + let mut releases = entries + .filter_map(|entry| entry.ok()) + .filter_map(|entry| { + entry + .metadata() + .ok() + .map(|metadata| (entry.path(), metadata)) + }) + .filter_map(|(release_path, metadata)| { + if metadata.is_dir() { + Some((release_path, metadata)) + } else { + None + } + }) + .filter_map(|(release_path, metadata)| { + metadata + .modified() + .ok() + .map(|modified_time| (release_path, modified_time)) + }) + .collect::>(); + releases.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); // order by newest releases + + let old_releases = releases.split_off(5); // Delete all but the 5 newest releases + + if !old_releases.is_empty() { + let progress_bar = new_spinner_progress_bar(); + progress_bar.set_length(old_releases.len() as u64); + progress_bar.set_style( + ProgressStyle::default_bar() + .template(&format!( + "{}{}{}", + "{spinner:.green} ", + RECYCLING, + "Removing old releases [{bar:40.cyan/blue}] {pos}/{len} ({eta})" + )) + .progress_chars("=> "), + ); + for (release, _modified_type) in old_releases { + progress_bar.inc(1); + let _ = fs::remove_dir_all(&release); + } + progress_bar.finish_and_clear(); + } + + Ok(()) +} + pub fn update(config_file: &str) -> Result { let mut config = Config::load(config_file)?; let update_manifest = info(config_file, false, false)?; @@ -917,6 +973,13 @@ pub fn update(config_file: &str) -> Result { return Err(format!("Incompatible update target: {}", release_target)); } + // Trigger an update to the modification time for `release_dir` + { + let path = &release_dir.join(".touch"); + let _ = fs::OpenOptions::new().create(true).write(true).open(path); + let _ = fs::remove_file(path); + } + let _ = fs::remove_dir_all(config.active_release_dir()); symlink_dir( release_dir.join("solana-release"), @@ -932,6 +995,7 @@ pub fn update(config_file: &str) -> Result { })?; config.save(config_file)?; + gc(config_file)?; println!(" {}{}", SPARKLE, style("Update successful").bold()); Ok(true) diff --git a/install/src/config.rs b/install/src/config.rs index 77130b8247..4871d565a2 100644 --- a/install/src/config.rs +++ b/install/src/config.rs @@ -18,7 +18,7 @@ pub struct Config { pub current_update_manifest: Option, pub update_poll_secs: u64, pub explicit_release: Option, - releases_dir: PathBuf, + pub releases_dir: PathBuf, active_release_dir: PathBuf, } diff --git a/install/src/lib.rs b/install/src/lib.rs index e1a4ce1b7d..f534e53957 100644 --- a/install/src/lib.rs +++ b/install/src/lib.rs @@ -208,6 +208,11 @@ pub fn main() -> Result<(), String> { .help("Keypair file for the update manifest (/path/to/keypair.json)"), ), ) + .subcommand( + SubCommand::with_name("gc") + .about("Delete older releases from the install cache to reclaim disk space") + .setting(AppSettings::DisableVersion), + ) .subcommand( SubCommand::with_name("update") .about("Checks for an update, and if available downloads and applies it") @@ -255,6 +260,7 @@ pub fn main() -> Result<(), String> { update_manifest_keypair_file, ) } + ("gc", Some(_matches)) => command::gc(config_file), ("update", Some(_matches)) => command::update(config_file).map(|_| ()), ("run", Some(matches)) => { let program_name = matches.value_of("program_name").unwrap();