vika_cli/
cache.rs

1use crate::error::{FileSystemError, Result};
2use serde::{Deserialize, Serialize};
3use std::path::PathBuf;
4use std::time::{SystemTime, UNIX_EPOCH};
5
6const CACHE_DIR: &str = ".vika-cache";
7const SPEC_CACHE_FILE: &str = "spec.json";
8const SPEC_META_FILE: &str = "spec.meta.json";
9
10#[derive(Debug, Serialize, Deserialize)]
11pub struct SpecMetadata {
12    pub url: String,
13    pub timestamp: u64,
14    pub etag: Option<String>,
15    pub content_hash: String,
16}
17
18pub struct CacheManager;
19
20impl CacheManager {
21    pub fn ensure_cache_dir() -> Result<PathBuf> {
22        let cache_dir = PathBuf::from(CACHE_DIR);
23        // create_dir_all succeeds if directory already exists
24        std::fs::create_dir_all(&cache_dir).map_err(|e| {
25            FileSystemError::CreateDirectoryFailed {
26                path: CACHE_DIR.to_string(),
27                source: e,
28            }
29        })?;
30        Ok(cache_dir)
31    }
32
33    pub fn get_cached_spec(url: &str) -> Result<Option<String>> {
34        let cache_dir = Self::ensure_cache_dir()?;
35        let meta_path = cache_dir.join(SPEC_META_FILE);
36        let spec_path = cache_dir.join(SPEC_CACHE_FILE);
37
38        // Check if cache exists
39        if !meta_path.exists() || !spec_path.exists() {
40            return Ok(None);
41        }
42
43        // Read metadata
44        let meta_content =
45            std::fs::read_to_string(&meta_path).map_err(|e| FileSystemError::ReadFileFailed {
46                path: meta_path.display().to_string(),
47                source: e,
48            })?;
49
50        let metadata: SpecMetadata =
51            serde_json::from_str(&meta_content).map_err(|_| FileSystemError::ReadFileFailed {
52                path: meta_path.display().to_string(),
53                source: std::io::Error::new(
54                    std::io::ErrorKind::InvalidData,
55                    "Invalid metadata format",
56                ),
57            })?;
58
59        // Check if URL matches
60        if metadata.url != url {
61            return Ok(None);
62        }
63
64        // Read cached spec
65        let spec_content =
66            std::fs::read_to_string(&spec_path).map_err(|e| FileSystemError::ReadFileFailed {
67                path: spec_path.display().to_string(),
68                source: e,
69            })?;
70
71        Ok(Some(spec_content))
72    }
73
74    pub fn cache_spec(url: &str, content: &str) -> Result<()> {
75        // Ensure cache directory exists before writing
76        let cache_dir = Self::ensure_cache_dir()?;
77        let meta_path = cache_dir.join(SPEC_META_FILE);
78        let spec_path = cache_dir.join(SPEC_CACHE_FILE);
79
80        // Compute content hash (simple hash for now)
81        use std::collections::hash_map::DefaultHasher;
82        use std::hash::{Hash, Hasher};
83        let mut hasher = DefaultHasher::new();
84        content.hash(&mut hasher);
85        let content_hash = format!("{:x}", hasher.finish());
86
87        // Get timestamp
88        let timestamp = SystemTime::now()
89            .duration_since(UNIX_EPOCH)
90            .unwrap()
91            .as_secs();
92
93        // Create metadata
94        let metadata = SpecMetadata {
95            url: url.to_string(),
96            timestamp,
97            etag: None, // Could be enhanced to fetch ETag from response
98            content_hash,
99        };
100
101        // Write metadata
102        let meta_json = serde_json::to_string_pretty(&metadata).map_err(|e| {
103            FileSystemError::WriteFileFailed {
104                path: meta_path.display().to_string(),
105                source: std::io::Error::new(std::io::ErrorKind::InvalidData, format!("{}", e)),
106            }
107        })?;
108
109        std::fs::write(&meta_path, meta_json).map_err(|e| FileSystemError::WriteFileFailed {
110            path: meta_path.display().to_string(),
111            source: e,
112        })?;
113
114        // Write spec content
115        std::fs::write(&spec_path, content).map_err(|e| FileSystemError::WriteFileFailed {
116            path: spec_path.display().to_string(),
117            source: e,
118        })?;
119
120        Ok(())
121    }
122
123    pub fn clear_cache() -> Result<()> {
124        let cache_dir = PathBuf::from(CACHE_DIR);
125        if cache_dir.exists() {
126            std::fs::remove_dir_all(&cache_dir).map_err(|e| {
127                FileSystemError::CreateDirectoryFailed {
128                    path: CACHE_DIR.to_string(),
129                    source: e,
130                }
131            })?;
132        }
133        Ok(())
134    }
135}