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 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 if !meta_path.exists() || !spec_path.exists() {
40 return Ok(None);
41 }
42
43 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 if metadata.url != url {
61 return Ok(None);
62 }
63
64 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 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 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 let timestamp = SystemTime::now()
89 .duration_since(UNIX_EPOCH)
90 .unwrap()
91 .as_secs();
92
93 let metadata = SpecMetadata {
95 url: url.to_string(),
96 timestamp,
97 etag: None, content_hash,
99 };
100
101 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 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}