From e4d5d5302e55bc4436c83c388bf6ed64157b2c62 Mon Sep 17 00:00:00 2001 From: kjuulh Date: Sat, 14 Sep 2024 21:17:20 +0200 Subject: [PATCH] feat: add cache get This now introduces the `settings.cache.duration` key, which can either be false, true (default) or a map `{days, hours, minutes}` for how long the cache should last. If the cache is expired an eager load of the repositories will be executed Signed-off-by: kjuulh --- crates/gitnow/src/cache.rs | 34 ++++++++++- crates/gitnow/src/commands/root.rs | 2 +- crates/gitnow/src/config.rs | 77 +++++++++++++++++++++++-- crates/gitnow/src/git_provider/gitea.rs | 3 - crates/gitnow/src/main.rs | 2 + 5 files changed, 106 insertions(+), 12 deletions(-) diff --git a/crates/gitnow/src/cache.rs b/crates/gitnow/src/cache.rs index 9596627..d8e1b88 100644 --- a/crates/gitnow/src/cache.rs +++ b/crates/gitnow/src/cache.rs @@ -52,7 +52,37 @@ impl Cache { return Ok(None); } - let file = tokio::fs::read(location).await?; + if let Some(cache_duration) = self.app.config.settings.cache.duration.get_duration() { + let metadata = tokio::fs::metadata(&location).await?; + + if let Ok(file_modified_last) = metadata + .modified() + .context("failed to get modified date") + .inspect_err(|e| { + tracing::warn!( + "could not get valid metadata from file, cache will be reused: {}", + e + ); + }) + .and_then(|m| { + m.elapsed() + .context("failed to get elapsed from file") + .inspect_err(|e| tracing::warn!("failed to get elapsed from system: {}", e)) + }) + { + tracing::trace!( + cache = file_modified_last.as_secs(), + expiry = cache_duration.as_secs(), + "checking if cache is valid" + ); + if file_modified_last > cache_duration { + tracing::debug!("cache has expired"); + return Ok(None); + } + } + } + + let file = tokio::fs::read(&location).await?; if file.is_empty() { tracing::debug!("cache file appears to be empty"); return Ok(None); @@ -87,7 +117,7 @@ pub trait CacheConfig { impl CacheConfig for Config { fn get_cache_location(&self) -> anyhow::Result { - Ok(self.settings.cache.location.clone()) + Ok(self.settings.cache.location.clone().into()) } fn get_cache_file_location(&self) -> anyhow::Result { diff --git a/crates/gitnow/src/commands/root.rs b/crates/gitnow/src/commands/root.rs index b071c20..2231532 100644 --- a/crates/gitnow/src/commands/root.rs +++ b/crates/gitnow/src/commands/root.rs @@ -27,7 +27,7 @@ impl RootCommand { }; for repo in &repositories { - tracing::info!("repo: {}", repo.to_rel_path().display()); + //tracing::info!("repo: {}", repo.to_rel_path().display()); } tracing::info!("amount of repos fetched {}", repositories.len()); diff --git a/crates/gitnow/src/config.rs b/crates/gitnow/src/config.rs index 3b31ded..1dc89b8 100644 --- a/crates/gitnow/src/config.rs +++ b/crates/gitnow/src/config.rs @@ -18,17 +18,76 @@ pub struct Settings { pub cache: Cache, } -#[derive(Debug, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Default, Serialize, Deserialize, PartialEq)] pub struct Cache { - pub location: PathBuf, + #[serde(default)] + pub location: CacheLocation, + + #[serde(default)] + pub duration: CacheDuration, } -impl Default for Cache { +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct CacheLocation(PathBuf); + +impl From for CacheLocation { + fn from(value: PathBuf) -> Self { + Self(value) + } +} + +impl From for PathBuf { + fn from(value: CacheLocation) -> Self { + value.0 + } +} + +impl Default for CacheLocation { fn default() -> Self { let home = dirs::home_dir().unwrap_or_default(); - Self { - location: home.join(".cache/gitnow"), + Self(home.join(".cache/gitnow")) + } +} + +#[derive(Debug, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum CacheDuration { + Enabled(bool), + Precise { + #[serde(default)] + days: u64, + #[serde(default)] + hours: u64, + #[serde(default)] + minutes: u64, + }, +} + +impl CacheDuration { + pub fn get_duration(&self) -> Option { + match self { + CacheDuration::Enabled(true) => CacheDuration::default().get_duration(), + CacheDuration::Enabled(false) => None, + CacheDuration::Precise { + days, + hours, + minutes, + } => Some( + std::time::Duration::from_days(*days) + + std::time::Duration::from_hours(*hours) + + std::time::Duration::from_mins(*minutes), + ), + } + } +} + +impl Default for CacheDuration { + fn default() -> Self { + Self::Precise { + days: 1, + hours: 0, + minutes: 0, } } } @@ -174,6 +233,7 @@ mod test { let content = r#" [settings.cache] location = ".cache/gitnow" + duration = { days = 2 } [[providers.github]] current_user = "kjuulh" @@ -250,7 +310,12 @@ mod test { }, settings: Settings { cache: Cache { - location: PathBuf::from(".cache/gitnow/") + location: PathBuf::from(".cache/gitnow").into(), + duration: CacheDuration::Precise { + days: 2, + hours: 0, + minutes: 0 + } } } }, diff --git a/crates/gitnow/src/git_provider/gitea.rs b/crates/gitnow/src/git_provider/gitea.rs index 67e0f6e..33c2ca6 100644 --- a/crates/gitnow/src/git_provider/gitea.rs +++ b/crates/gitnow/src/git_provider/gitea.rs @@ -120,7 +120,6 @@ impl GiteaProvider { .collect()) } - #[tracing::instrument(skip(self))] pub async fn list_repositories_for_user_with_page( &self, user: &str, @@ -135,7 +134,6 @@ impl GiteaProvider { Ok(repos) } - #[tracing::instrument] pub async fn list_repositories_for_organisation( &self, organisation: &str, @@ -181,7 +179,6 @@ impl GiteaProvider { .collect()) } - #[tracing::instrument(skip(self))] pub async fn list_repositories_for_organisation_with_page( &self, organisation: &str, diff --git a/crates/gitnow/src/main.rs b/crates/gitnow/src/main.rs index 6146ae1..0a00951 100644 --- a/crates/gitnow/src/main.rs +++ b/crates/gitnow/src/main.rs @@ -1,3 +1,5 @@ +#![feature(duration_constructors)] + use std::path::PathBuf; use anyhow::Context;