Skip to main content

fastskill_core/core/
build_cache.rs

1//! Build cache management for tracking skill versions and hashes
2
3use crate::core::service::ServiceError;
4use chrono::Utc;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use std::path::Path;
8
9/// Build cache structure for tracking skill versions and hashes
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct BuildCache {
12    pub version: String,
13    #[serde(default)]
14    pub last_build: Option<String>,
15    #[serde(default)]
16    pub skills: HashMap<String, SkillCacheEntry>,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct SkillCacheEntry {
21    pub version: String,
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub previous_version: Option<String>,
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub hash: Option<String>,
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub last_packaged: Option<String>,
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub artifact_path: Option<String>,
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub git_commit: Option<String>,
32}
33
34impl Default for BuildCache {
35    fn default() -> Self {
36        Self {
37            version: "1.0".to_string(),
38            last_build: None,
39            skills: HashMap::new(),
40        }
41    }
42}
43
44impl BuildCache {
45    /// Load build cache from file
46    pub fn load(cache_path: &Path) -> Result<Self, ServiceError> {
47        if !cache_path.exists() {
48            return Ok(Self::default());
49        }
50
51        let content = std::fs::read_to_string(cache_path).map_err(ServiceError::Io)?;
52
53        let cache: BuildCache = serde_json::from_str(&content)
54            .map_err(|e| ServiceError::Custom(format!("Failed to parse build cache: {}", e)))?;
55
56        Ok(cache)
57    }
58
59    /// Save build cache to file
60    pub fn save(&self, cache_path: &Path) -> Result<(), ServiceError> {
61        // Create parent directory if it doesn't exist
62        if let Some(parent) = cache_path.parent() {
63            std::fs::create_dir_all(parent).map_err(ServiceError::Io)?;
64        }
65
66        let content = serde_json::to_string_pretty(self)
67            .map_err(|e| ServiceError::Custom(format!("Failed to serialize build cache: {}", e)))?;
68
69        std::fs::write(cache_path, content).map_err(ServiceError::Io)?;
70
71        Ok(())
72    }
73
74    /// Update cache entry for a skill
75    pub fn update_skill(
76        &mut self,
77        skill_id: &str,
78        version: &str,
79        hash: &str,
80        artifact_path: &Path,
81        git_commit: Option<&str>,
82    ) {
83        let previous_version = self.skills.get(skill_id).map(|e| e.version.clone());
84
85        let entry = SkillCacheEntry {
86            version: version.to_string(),
87            previous_version,
88            hash: Some(hash.to_string()),
89            last_packaged: Some(Utc::now().to_rfc3339()),
90            artifact_path: Some(artifact_path.to_string_lossy().to_string()),
91            git_commit: git_commit.map(|s| s.to_string()),
92        };
93
94        self.skills.insert(skill_id.to_string(), entry);
95        self.last_build = Some(Utc::now().to_rfc3339());
96    }
97
98    /// Get cached version for a skill
99    pub fn get_cached_version(&self, skill_id: &str) -> Option<String> {
100        self.skills.get(skill_id).map(|e| e.version.clone())
101    }
102
103    /// Get cached hash for a skill
104    pub fn get_cached_hash(&self, skill_id: &str) -> Option<String> {
105        self.skills.get(skill_id).and_then(|e| e.hash.clone())
106    }
107}