From 53c5308364716ce7af51f0ae9814761a0b8e877b Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Mercier Date: Sun, 19 Jan 2025 11:42:02 +0100 Subject: [PATCH] feat: add http cache --- docs/tasks/toml-tasks.md | 7 +++- src/cli/run.rs | 2 +- src/task/file_providers/http_file_provider.rs | 37 +++++++++++++------ src/task/file_providers/mod.rs | 22 +++++------ 4 files changed, 41 insertions(+), 27 deletions(-) diff --git a/docs/tasks/toml-tasks.md b/docs/tasks/toml-tasks.md index e30b2e6dd7..7076c472f7 100644 --- a/docs/tasks/toml-tasks.md +++ b/docs/tasks/toml-tasks.md @@ -341,8 +341,11 @@ Task files can be fetched via http: file = "https://example.com/build.sh" ``` -Currently, they're fetched everytime they're executed, but we may add some cache support later. -This could be extended with other protocols like mentioned in [this ticket](https://github.com/jdx/mise/issues/2488) if there were interest. +Each task file is cached in the `MISE_CACHE_DIR` directory. If the file is updated, it will not be re-downloaded unless the cache is cleared. + +:::tip +You can reset the cache by running `mise cache clear`. +::: ## Arguments diff --git a/src/cli/run.rs b/src/cli/run.rs index 8c36126a30..fb50373dc0 100644 --- a/src/cli/run.rs +++ b/src/cli/run.rs @@ -873,7 +873,7 @@ impl Run { } fn fetch_tasks(&self, tasks: &mut Vec) -> Result<()> { - let task_file_providers = TaskFileProviders::new(self.tmpdir.clone()); + let task_file_providers = TaskFileProviders; for t in tasks { if let Some(file) = &t.file { diff --git a/src/task/file_providers/http_file_provider.rs b/src/task/file_providers/http_file_provider.rs index 3df8488bad..212c53963f 100644 --- a/src/task/file_providers/http_file_provider.rs +++ b/src/task/file_providers/http_file_provider.rs @@ -1,17 +1,20 @@ use std::path::PathBuf; +use md5::Digest; +use sha2::Sha256; + use crate::{file, http::HTTP}; use super::TaskFileProvider; #[derive(Debug)] pub struct HttpTaskFileProvider { - tmpdir: PathBuf, + cache_path: PathBuf, } impl HttpTaskFileProvider { - pub fn new(tmpdir: PathBuf) -> Self { - Self { tmpdir } + pub fn new(cache_path: PathBuf) -> Self { + Self { cache_path } } } @@ -21,15 +24,25 @@ impl TaskFileProvider for HttpTaskFileProvider { } fn get_local_path(&self, file: &str) -> Result> { - let url = url::Url::parse(file)?; - let filename = url - .path_segments() - .and_then(|segments| segments.last()) - .unwrap(); - let tmp_path = self.tmpdir.join(filename); - HTTP.download_file(file, &tmp_path, None)?; - file::make_executable(&tmp_path)?; - Ok(tmp_path) + // Cache key is the full URL in sha256 + let mut hasher = Sha256::new(); + hasher.update(file); + let cache_key = format!("{:x}", hasher.finalize()); + let cached_file_path = self.cache_path.join(&cache_key); + + if cached_file_path.exists() { + debug!("Using cached file: {:?}", cached_file_path); + if let Ok(path) = cached_file_path.canonicalize() { + return Ok(path); + } + } + + debug!("Downloading file: {}", file); + + HTTP.download_file(file, &cached_file_path, None)?; + file::make_executable(&cached_file_path)?; + + Ok(cached_file_path) } } diff --git a/src/task/file_providers/mod.rs b/src/task/file_providers/mod.rs index cd5707d084..6573baf896 100644 --- a/src/task/file_providers/mod.rs +++ b/src/task/file_providers/mod.rs @@ -1,3 +1,4 @@ +use std::sync::LazyLock as Lazy; use std::{fmt::Debug, path::PathBuf}; mod http_file_provider; @@ -6,27 +7,25 @@ mod local_file_provider; pub use http_file_provider::HttpTaskFileProvider; pub use local_file_provider::LocalTaskFileProvider; +use crate::dirs; + +static CACHE_FILE_PROVIDERS: Lazy = Lazy::new(|| dirs::CACHE.join("tasks-file-provider")); + pub trait TaskFileProvider: Debug { fn is_match(&self, file: &str) -> bool; fn get_local_path(&self, file: &str) -> Result>; } -pub struct TaskFileProviders { - tmpdir: PathBuf, -} +pub struct TaskFileProviders; impl TaskFileProviders { fn get_providers(&self) -> Vec> { vec![ - Box::new(HttpTaskFileProvider::new(self.tmpdir.clone())), + Box::new(HttpTaskFileProvider::new(CACHE_FILE_PROVIDERS.clone())), Box::new(LocalTaskFileProvider), // Must be the last provider ] } - pub fn new(tmpdir: PathBuf) -> Self { - Self { tmpdir } - } - pub fn get_provider(&self, file: &str) -> Option> { self.get_providers().into_iter().find(|p| p.is_match(file)) } @@ -34,20 +33,19 @@ impl TaskFileProviders { #[cfg(test)] mod tests { - use std::env; use super::*; #[test] fn test_get_providers() { - let task_file_providers = TaskFileProviders::new(env::temp_dir()); + let task_file_providers = TaskFileProviders; let providers = task_file_providers.get_providers(); assert_eq!(providers.len(), 2); } #[test] fn test_local_file_match_local_provider() { - let task_file_providers = TaskFileProviders::new(env::temp_dir()); + let task_file_providers = TaskFileProviders; let cases = vec!["file.txt", "./file.txt", "../file.txt", "/file.txt"]; for file in cases { @@ -59,7 +57,7 @@ mod tests { #[test] fn test_http_file_match_http_provider() { - let task_file_providers = TaskFileProviders::new(env::temp_dir()); + let task_file_providers = TaskFileProviders; let cases = vec![ "http://example.com/file.txt", "https://example.com/file.txt",