use std::path::{Path, PathBuf};
use crate::cache::caches::*;
use crate::cache::*;
use crate::library::*;
use crate::remove::*;
use humansize::{file_size_opts, FileSize};
use walkdir::WalkDir;
fn get_last_access_of_item(path: &Path) -> std::time::SystemTime {
if path.is_file() {
std::fs::metadata(path).unwrap().accessed().unwrap()
} else {
WalkDir::new(path)
.into_iter()
.map(|e| e.unwrap().path().to_owned())
.map(|filepath| std::fs::metadata(filepath).unwrap().accessed().unwrap()) .max()
.unwrap()
}
}
pub(crate) fn gather_all_cache_items<'a>(
git_checkouts_cache: &'a mut git_checkouts::GitCheckoutCache,
bare_repos_cache: &'a mut git_bare_repos::GitRepoCache,
registry_pkg_cache: &'a mut registry_pkg_cache::RegistryPkgCaches,
registry_sources_cache: &'a mut registry_sources::RegistrySourceCaches,
) -> Vec<&'a PathBuf> {
let mut all_items: Vec<&PathBuf> = Vec::new();
all_items.extend(git_checkouts_cache.items());
all_items.extend(bare_repos_cache.items());
all_items.extend(registry_pkg_cache.items());
all_items.extend(registry_sources_cache.items());
all_items.sort_by_cached_key(|path| get_last_access_of_item(path));
all_items.reverse();
all_items
}
fn parse_size_limit_to_bytes(limit: Option<&str>) -> Result<u64, Error> {
match limit {
None => unreachable!("No trim --limit was supplied although clap should enforce that!"),
Some(limit) => {
let unit_multiplicator: Result<u64, Error> = match limit.chars().last() {
None => Ok(0),
Some(c) => {
if c.is_alphabetic() {
match c {
'b' | 'B' => Ok(1),
'k' | 'K' => Ok(1024),
'm' | 'M' => Ok(1024 * 1024),
'g' | 'G' => Ok(1024 * 1024 * 1024),
't' | 'T' => Ok(1024 * 1024 * 1024 * 1024),
_ => Err(Error::TrimLimitUnitParseFailure(limit.to_string())),
}
} else {
Err(Error::TrimLimitUnitParseFailure(limit.to_string()))
}
}
};
let value: f64 = match limit[0..(limit.len() - 1)].parse() {
Ok(val) => val,
Err(_) => {
return Err(Error::TrimLimitUnitParseFailure(limit.to_string()));
}
};
if value == 0.0 {
return Ok(0);
}
#[allow(clippy::cast_lossless)]
#[allow(clippy::cast_sign_loss)]
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_precision_loss)]
Ok((value * unit_multiplicator? as f64) as u64)
}
}
}
pub(crate) fn trim_cache<'a>(
unparsed_size_limit: Option<&'a str>,
git_checkouts_cache: &mut git_checkouts::GitCheckoutCache,
bare_repos_cache: &mut git_bare_repos::GitRepoCache,
registry_pkg_cache: &mut registry_pkg_cache::RegistryPkgCaches,
registry_sources_cache: &mut registry_sources::RegistrySourceCaches,
dry_run: bool,
size_changed: &mut bool,
) -> Result<(), Error> {
let size_limit = parse_size_limit_to_bytes(unparsed_size_limit)?;
let total_cache_size: u64 = git_checkouts_cache.total_size()
+ bare_repos_cache.total_size()
+ registry_pkg_cache.total_size()
+ registry_sources_cache.total_size();
if size_limit > total_cache_size {
return Ok(());
}
let all_cache_items: Vec<&PathBuf> = gather_all_cache_items(
git_checkouts_cache,
bare_repos_cache,
registry_pkg_cache,
registry_sources_cache,
);
let mut cache_size = 0;
let mut removed_size: u64 = 0;
let mut removed_item_count = 0;
all_cache_items
.iter()
.filter(|path| {
let item_size = size_of_path(path);
cache_size += item_size;
let keep_file = cache_size > size_limit;
if keep_file {
removed_size += item_size;
removed_item_count += 1;
}
keep_file
})
.for_each(|path| {
remove_file(
path,
dry_run,
size_changed,
None,
&DryRunMessage::Default,
None,
);
});
git_checkouts_cache.invalidate();
bare_repos_cache.invalidate();
registry_pkg_cache.invalidate();
registry_sources_cache.invalidate();
println!(
"Removed {} items totalling {}",
removed_item_count,
removed_size.file_size(file_size_opts::DECIMAL).unwrap()
);
Ok(())
}
#[cfg(test)]
mod parse_size_limit {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn size_limit() {
fn p(limit: Option<&str>) -> Result<u64, Error> {
parse_size_limit_to_bytes(limit)
}
assert_eq!(p(Some("1b")).unwrap(), 1);
assert_eq!(p(Some("1B")).unwrap(), 1);
assert_eq!(p(Some("1k")).unwrap(), 1_024);
assert_eq!(p(Some("1K")).unwrap(), 1_024);
assert_eq!(p(Some("1m")).unwrap(), 1_048_576);
assert_eq!(p(Some("1M")).unwrap(), 1_048_576);
assert_eq!(p(Some("1g")).unwrap(), 1_073_741_824);
assert_eq!(p(Some("1G")).unwrap(), 1_073_741_824);
assert_eq!(p(Some("1t")).unwrap(), 1_099_511_627_776);
assert_eq!(p(Some("1T")).unwrap(), 1_099_511_627_776);
assert_eq!(p(Some("4M")).unwrap(), 4_194_304);
assert_eq!(p(Some("42M")).unwrap(), 44_040_192);
assert_eq!(p(Some("1337M")).unwrap(), 1_401_946_112);
assert_eq!(p(Some("1.5k")).unwrap(), 1_536);
match p(Some("1_")) {
Ok(_) => panic!("expected error"),
Err(Error::TrimLimitUnitParseFailure(string)) => assert_eq!(string, "1_"),
Err(..) => panic!("did not get enum variant TrimParseLimitUnitParseFailure"),
}
}
#[test]
#[should_panic(expected = "No trim --limit was supplied although clap should enforce that!")]
fn size_limit_none_panics() {
let _ignore = parse_size_limit_to_bytes(None);
}
}