use crate::cache::caches::{Cache, RegistrySuperCache};
use crate::cache::*;
use crate::library::*;
use crate::remove::*;
use chrono::{prelude::*, NaiveDateTime};
use regex::Regex;
#[derive(Debug, Clone)]
enum AgeRelation<'a> {
None,
FileOlderThanDate(&'a str),
FileYoungerThanDate(&'a str),
}
fn parse_date(date: &str) -> Result<NaiveDateTime, Error> {
let date_to_compare: NaiveDateTime = {
if Regex::new(r"^\d{4}.\d{2}.\d{2}$").unwrap().is_match(date) {
let now = Local::now();
let split: Result<Vec<u32>, _> = date.split('.').map(str::parse).collect();
let split = match split {
Ok(result) => result,
Err(a) => return Err(Error::DateParseFailure(a.to_string(), "u32".into())),
};
#[allow(clippy::cast_possible_wrap)]
let nd =
if let Some(date2) = NaiveDate::from_ymd_opt(split[0] as i32, split[1], split[2]) {
date2
} else {
return Err(Error::DateParseFailure(
format!("{}.{}.{}", split[0], split[1], split[2]),
"date".into(),
));
};
nd.and_hms(now.hour(), now.minute(), now.second())
} else if Regex::new(r"^\d{2}:\d{2}:\d{2}$").unwrap().is_match(date) {
let today = Local::today();
let split: Result<Vec<u32>, _> = date.split(':').map(str::parse).collect();
let split = match split {
Ok(result) => result,
Err(a) => return Err(Error::DateParseFailure(a.to_string(), "u32".into())),
};
let nd = if let Some(date2) =
NaiveDate::from_ymd_opt(today.year(), today.month(), today.day())
{
date2
} else {
return Err(Error::DateParseFailure(
format!("{}:{}:{}", today.year(), today.month(), today.day()),
"date".into(),
));
};
nd.and_hms(split[0], split[1], split[2])
} else {
return Err(Error::DateParseFailure(date.into(), "a valid date".into()));
}
};
Ok(date_to_compare)
}
#[derive(Debug, Clone)]
struct FileWithDate {
file: std::path::PathBuf,
access_date: NaiveDateTime,
}
fn filter_files_by_date<'a>(
date: &AgeRelation<'_>,
files: &'a [FileWithDate],
) -> Result<Vec<&'a FileWithDate>, Error> {
match date {
AgeRelation::None => {
unreachable!("ERROR: no dates were supplied although -o or -y were passed!");
}
AgeRelation::FileYoungerThanDate(younger_date) => {
let date_parameter = parse_date(younger_date)?;
Ok(files
.iter()
.filter(|file| file.access_date > date_parameter)
.collect())
}
AgeRelation::FileOlderThanDate(older_date) => {
let date_parameter = parse_date(older_date)?;
Ok(files
.iter()
.filter(|file| file.access_date < date_parameter)
.collect())
}
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn remove_files_by_dates(
checkouts_cache: &mut git_checkouts::GitCheckoutCache,
bare_repos_cache: &mut git_bare_repos::GitRepoCache,
registry_pkg_caches: &mut registry_pkg_cache::RegistryPkgCaches,
registry_sources_caches: &mut registry_sources::RegistrySourceCaches,
arg_younger: Option<&str>,
arg_older: Option<&str>,
dry_run: bool,
dirs: Option<&str>,
size_changed: &mut bool,
) -> Result<(), Error> {
if dirs.is_none() {
return Err(Error::RemoveDirNoArg);
}
let components_to_remove_from = components_from_groups(dirs)?;
let mut files_of_components: Vec<std::path::PathBuf> = Vec::new();
components_to_remove_from.iter().for_each(|component| {
match component {
Component::RegistryCrateCache => {
files_of_components.extend(registry_pkg_caches.files());
}
Component::RegistrySources => {
files_of_components.extend(registry_sources_caches.files());
}
Component::RegistryIndex => { }
Component::GitRepos => {
files_of_components.extend(checkouts_cache.items().iter().cloned());
}
Component::GitDB => {
files_of_components.extend(bare_repos_cache.items().iter().cloned());
}
}
});
let date_comp: AgeRelation<'_> = match (arg_older, arg_younger) {
(None, None) => AgeRelation::None,
(None, Some(younger)) => AgeRelation::FileYoungerThanDate(younger),
(Some(older), None) => AgeRelation::FileOlderThanDate(older),
(Some(_older), Some(_younger)) => {
unreachable!(
"{}",
"passing both, --remove-if-{older,younger}-than was temporarily disabled!"
)
} };
let mut dates: Vec<FileWithDate> = files_of_components
.into_iter()
.map(|path| {
let access_time = path.metadata().unwrap().accessed().unwrap();
let naive_datetime = chrono::DateTime::<Local>::from(access_time).naive_local();
FileWithDate {
file: path,
access_date: naive_datetime,
}
})
.collect();
dates.sort_by_key(|f| f.file.clone());
let filtered_files: Vec<&FileWithDate> = filter_files_by_date(&date_comp, &dates)?;
if dry_run {
println!(
"dry-run: would delete {} items that are {}...",
filtered_files.len(),
match date_comp {
AgeRelation::FileYoungerThanDate(date) => format!("younger than {}", date),
AgeRelation::FileOlderThanDate(date) => format!("older than {}", date),
AgeRelation::None => unreachable!(
"DateComparisonOlder and Younger or None not supported right now (dry run)"
),
},
);
} else {
println!(
"Deleting {} items that are {}...",
filtered_files.len(),
match date_comp {
AgeRelation::FileYoungerThanDate(date) => format!("younger than {}", date),
AgeRelation::FileOlderThanDate(date) => format!("older than {}", date),
AgeRelation::None => unreachable!(
"DateComparisonOlder and Younger or None not supported right now (no dry run)"
),
},
);
filtered_files
.into_iter()
.map(|fwd| &fwd.file)
.for_each(|path| {
remove_file(
path,
false,
size_changed,
None,
&DryRunMessage::Default,
None,
);
});
components_to_remove_from.iter().for_each(|component| {
match component {
Component::RegistryCrateCache => {
registry_pkg_caches.invalidate();
}
Component::RegistrySources => {
registry_sources_caches.invalidate();
}
Component::RegistryIndex => { }
Component::GitRepos => {
checkouts_cache.invalidate();
}
Component::GitDB => {
bare_repos_cache.invalidate();
}
}
});
}
Ok(())
}
#[cfg(test)]
mod libtests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn parse_dates() {
assert!(parse_date("").is_err());
assert!(parse_date(&String::from("a")).is_err());
assert!(parse_date(&String::from("01.01:2002")).is_err());
assert!(parse_date(&String::from("01.01.2002")).is_err()); assert!(parse_date(&String::from("2002.30.30")).is_err());
assert_eq!(
parse_date(&String::from("2002.01.01"))
.unwrap()
.format("%Y.%m.%d")
.to_string(),
String::from("2002.01.01")
);
assert_eq!(
parse_date(&String::from("1234.12.08"))
.unwrap()
.format("%Y.%m.%d")
.to_string(),
String::from("1234.12.08")
);
assert_eq!(
parse_date(&String::from("1990.12.08"))
.unwrap()
.format("%Y.%m.%d")
.to_string(),
String::from("1990.12.08")
);
assert_eq!(
parse_date(&String::from("12:00:00"))
.unwrap()
.format("%H:%M:%S")
.to_string(),
String::from("12:00:00")
);
assert_eq!(
parse_date(&String::from("00:00:00"))
.unwrap()
.format("%H:%M:%S")
.to_string(),
String::from("00:00:00")
);
}
#[test]
#[should_panic(expected = "invalid time")]
fn parse_dates_panic1() {
assert!(parse_date(&String::from("24:00:00")).is_err());
}
#[test]
#[should_panic(expected = "invalid time")]
fn parse_dates_panic2() {
assert!(parse_date(&String::from("24:30:24")).is_err());
}
#[test]
#[should_panic(expected = "invalid time")]
fn parse_dates_panic3() {
assert!(parse_date(&String::from("30:30:24")).is_err());
}
}