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";
7
8fn cache_key(spec_path: &str, spec_name: Option<&str>) -> String {
10 if let Some(name) = spec_name {
11 format!("{}:{}", name, spec_path)
12 } else {
13 spec_path.to_string()
14 }
15}
16
17fn cache_file_names(cache_key: &str) -> (String, String) {
19 use std::collections::hash_map::DefaultHasher;
20 use std::hash::{Hash, Hasher};
21 let mut hasher = DefaultHasher::new();
22 cache_key.hash(&mut hasher);
23 let hash = format!("{:x}", hasher.finish());
24 (
25 format!("spec_{}.json", hash),
26 format!("spec_{}.meta.json", hash),
27 )
28}
29
30#[derive(Debug, Serialize, Deserialize)]
31pub struct SpecMetadata {
32 pub url: String,
33 pub timestamp: u64,
34 pub etag: Option<String>,
35 pub content_hash: String,
36}
37
38pub struct CacheManager;
39
40impl CacheManager {
41 pub fn ensure_cache_dir() -> Result<PathBuf> {
42 let cache_dir = PathBuf::from(CACHE_DIR);
43 std::fs::create_dir_all(&cache_dir).map_err(|e| {
45 FileSystemError::CreateDirectoryFailed {
46 path: CACHE_DIR.to_string(),
47 source: e,
48 }
49 })?;
50 Ok(cache_dir)
51 }
52
53 pub fn get_cached_spec(url: &str) -> Result<Option<String>> {
54 Self::get_cached_spec_with_name(url, None)
55 }
56
57 pub fn get_cached_spec_with_name(url: &str, spec_name: Option<&str>) -> Result<Option<String>> {
58 let cache_dir = Self::ensure_cache_dir()?;
59 let key = cache_key(url, spec_name);
60 let (spec_file, meta_file) = cache_file_names(&key);
61 let meta_path = cache_dir.join(&meta_file);
62 let spec_path = cache_dir.join(&spec_file);
63
64 if !meta_path.exists() || !spec_path.exists() {
66 return Ok(None);
67 }
68
69 let meta_content =
71 std::fs::read_to_string(&meta_path).map_err(|e| FileSystemError::ReadFileFailed {
72 path: meta_path.display().to_string(),
73 source: e,
74 })?;
75
76 let metadata: SpecMetadata =
77 serde_json::from_str(&meta_content).map_err(|_| FileSystemError::ReadFileFailed {
78 path: meta_path.display().to_string(),
79 source: std::io::Error::new(
80 std::io::ErrorKind::InvalidData,
81 "Invalid metadata format",
82 ),
83 })?;
84
85 if metadata.url != url {
87 return Ok(None);
88 }
89
90 let spec_content =
92 std::fs::read_to_string(&spec_path).map_err(|e| FileSystemError::ReadFileFailed {
93 path: spec_path.display().to_string(),
94 source: e,
95 })?;
96
97 Ok(Some(spec_content))
98 }
99
100 pub fn cache_spec(url: &str, content: &str) -> Result<()> {
101 Self::cache_spec_with_name(url, content, None)
102 }
103
104 pub fn cache_spec_with_name(url: &str, content: &str, spec_name: Option<&str>) -> Result<()> {
105 let cache_dir = Self::ensure_cache_dir()?;
107 let key = cache_key(url, spec_name);
108 let (spec_file, meta_file) = cache_file_names(&key);
109 let meta_path = cache_dir.join(&meta_file);
110 let spec_path = cache_dir.join(&spec_file);
111
112 use std::collections::hash_map::DefaultHasher;
114 use std::hash::{Hash, Hasher};
115 let mut hasher = DefaultHasher::new();
116 content.hash(&mut hasher);
117 let content_hash = format!("{:x}", hasher.finish());
118
119 let timestamp = SystemTime::now()
121 .duration_since(UNIX_EPOCH)
122 .unwrap()
123 .as_secs();
124
125 let metadata = SpecMetadata {
127 url: url.to_string(),
128 timestamp,
129 etag: None, content_hash,
131 };
132
133 let meta_json = serde_json::to_string_pretty(&metadata).map_err(|e| {
135 FileSystemError::WriteFileFailed {
136 path: meta_path.display().to_string(),
137 source: std::io::Error::new(std::io::ErrorKind::InvalidData, format!("{}", e)),
138 }
139 })?;
140
141 std::fs::write(&meta_path, meta_json).map_err(|e| FileSystemError::WriteFileFailed {
142 path: meta_path.display().to_string(),
143 source: e,
144 })?;
145
146 std::fs::write(&spec_path, content).map_err(|e| FileSystemError::WriteFileFailed {
148 path: spec_path.display().to_string(),
149 source: e,
150 })?;
151
152 Ok(())
153 }
154
155 pub fn clear_cache() -> Result<()> {
156 let cache_dir = PathBuf::from(CACHE_DIR);
157 if cache_dir.exists() {
158 std::fs::remove_dir_all(&cache_dir).map_err(|e| {
159 FileSystemError::CreateDirectoryFailed {
160 path: CACHE_DIR.to_string(),
161 source: e,
162 }
163 })?;
164 }
165 Ok(())
166 }
167}