- Add `.gitignore` entries for `webmeta.json` and `.vscode` - Improve collection scanning logging for better debugging - Improve error handling in collection methods for robustness
183 lines
4.8 KiB
Rust
183 lines
4.8 KiB
Rust
use lazy_static::lazy_static;
|
|
use sal::git::{GitRepo, GitTree};
|
|
use std::collections::HashMap;
|
|
use std::path::{Path, PathBuf};
|
|
use std::sync::{Arc, Mutex};
|
|
use std::time::{ SystemTime};
|
|
|
|
use crate::error::{Result, WebBuilderError};
|
|
|
|
// Cache entry for Git repositories
|
|
struct CacheEntry {
|
|
path: PathBuf,
|
|
last_updated: SystemTime,
|
|
}
|
|
|
|
// Global cache for Git repositories
|
|
lazy_static! {
|
|
static ref REPO_CACHE: Arc<Mutex<HashMap<String, CacheEntry>>> =
|
|
Arc::new(Mutex::new(HashMap::new()));
|
|
}
|
|
|
|
// Cache timeout in seconds (default: 1 hour)
|
|
const CACHE_TIMEOUT: u64 = 3600;
|
|
|
|
/// Clone a Git repository
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `url` - URL of the repository to clone
|
|
/// * `destination` - Destination directory
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// The path to the cloned repository or an error
|
|
pub fn clone_repository<P: AsRef<Path>>(url: &str, destination: P) -> Result<PathBuf> {
|
|
let destination = destination.as_ref();
|
|
let destination_str = destination.to_str().unwrap();
|
|
|
|
// Create a GitTree for the parent directory
|
|
let parent_dir = destination.parent().ok_or_else(|| {
|
|
WebBuilderError::InvalidConfiguration(format!(
|
|
"Invalid destination path: {}",
|
|
destination_str
|
|
))
|
|
})?;
|
|
|
|
let git_tree = GitTree::new(parent_dir.to_str().unwrap())
|
|
.map_err(|e| WebBuilderError::GitError(format!("Failed to create GitTree: {}", e)))?;
|
|
|
|
// Use the GitTree to get (clone) the repository
|
|
let repos = git_tree
|
|
.get(url)
|
|
.map_err(|e| WebBuilderError::GitError(format!("Failed to clone repository: {}", e)))?;
|
|
|
|
if repos.is_empty() {
|
|
return Err(WebBuilderError::GitError(format!(
|
|
"Failed to clone repository: No repository was created"
|
|
)));
|
|
}
|
|
|
|
// Return the path of the first repository
|
|
Ok(PathBuf::from(repos[0].path()))
|
|
}
|
|
|
|
/// Pull the latest changes from a Git repository
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `path` - Path to the repository
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// Ok(()) on success or an error
|
|
pub fn pull_repository<P: AsRef<Path>>(path: P) -> Result<()> {
|
|
let path = path.as_ref();
|
|
let path_str = path.to_str().unwrap();
|
|
|
|
// Create a GitRepo directly
|
|
let repo = GitRepo::new(path_str.to_string());
|
|
|
|
// Pull the repository
|
|
repo.pull()
|
|
.map_err(|e| WebBuilderError::GitError(format!("Failed to pull repository: {}", e)))?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Clone or pull a Git repository with caching
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `url` - URL of the repository to clone
|
|
/// * `destination` - Destination directory
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// The path to the repository or an error
|
|
pub fn clone_or_pull<P: AsRef<Path>>(url: &str, destination: P) -> Result<PathBuf> {
|
|
let destination = destination.as_ref();
|
|
|
|
// Check the cache first
|
|
let mut cache = REPO_CACHE.lock().unwrap();
|
|
let now = SystemTime::now();
|
|
|
|
if let Some(entry) = cache.get(url) {
|
|
// Check if the cache entry is still valid
|
|
if let Ok(elapsed) = now.duration_since(entry.last_updated) {
|
|
if elapsed.as_secs() < CACHE_TIMEOUT {
|
|
// Cache is still valid, return the cached path
|
|
log::info!("Using cached repository for {}", url);
|
|
return Ok(entry.path.clone());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Cache miss or expired, clone or pull the repository
|
|
let result = if destination.exists() {
|
|
// Pull the repository
|
|
pull_repository(destination)?;
|
|
Ok(destination.to_path_buf())
|
|
} else {
|
|
// Clone the repository
|
|
clone_repository(url, destination)
|
|
};
|
|
|
|
// Update the cache
|
|
if let Ok(path) = &result {
|
|
cache.insert(
|
|
url.to_string(),
|
|
CacheEntry {
|
|
path: path.clone(),
|
|
last_updated: now,
|
|
},
|
|
);
|
|
}
|
|
|
|
result
|
|
}
|
|
|
|
/// Force update a Git repository, bypassing the cache
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `url` - URL of the repository to clone
|
|
/// * `destination` - Destination directory
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// The path to the repository or an error
|
|
pub fn force_update<P: AsRef<Path>>(url: &str, destination: P) -> Result<PathBuf> {
|
|
let destination = destination.as_ref();
|
|
|
|
// Clone or pull the repository
|
|
let result = if destination.exists() {
|
|
// Pull the repository
|
|
pull_repository(destination)?;
|
|
Ok(destination.to_path_buf())
|
|
} else {
|
|
// Clone the repository
|
|
clone_repository(url, destination)
|
|
};
|
|
|
|
// Update the cache
|
|
if let Ok(path) = &result {
|
|
let mut cache = REPO_CACHE.lock().unwrap();
|
|
cache.insert(
|
|
url.to_string(),
|
|
CacheEntry {
|
|
path: path.clone(),
|
|
last_updated: SystemTime::now(),
|
|
},
|
|
);
|
|
}
|
|
|
|
result
|
|
}
|
|
|
|
/// Clear the Git repository cache
|
|
pub fn clear_cache() {
|
|
let mut cache = REPO_CACHE.lock().unwrap();
|
|
cache.clear();
|
|
}
|