actr_cli/core/components/
cache_manager.rs1use anyhow::Result;
7use async_trait::async_trait;
8use std::path::PathBuf;
9
10use super::{CacheManager, CacheStats, CachedProto, Fingerprint, ProtoFile};
11
12pub struct DefaultCacheManager {
17 project_root: PathBuf,
19}
20
21impl DefaultCacheManager {
22 pub fn new() -> Self {
23 Self {
24 project_root: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
25 }
26 }
27
28 pub fn with_project_root(project_root: PathBuf) -> Self {
29 Self { project_root }
30 }
31
32 fn get_service_proto_dir(&self, service_name: &str) -> PathBuf {
35 self.project_root
36 .join("protos")
37 .join("remote")
38 .join(service_name)
39 }
40}
41
42impl Default for DefaultCacheManager {
43 fn default() -> Self {
44 Self::new()
45 }
46}
47
48#[async_trait]
49impl CacheManager for DefaultCacheManager {
50 async fn get_cached_proto(&self, service_name: &str) -> Result<Option<CachedProto>> {
51 let cache_path = self.get_service_proto_dir(service_name);
52
53 if !cache_path.exists() {
54 return Ok(None);
55 }
56
57 let mut files = Vec::new();
58 for entry in std::fs::read_dir(&cache_path)? {
59 let entry = entry?;
60 let path = entry.path();
61 if path.extension().map(|e| e == "proto").unwrap_or(false) {
62 let content = std::fs::read_to_string(&path)?;
63 files.push(ProtoFile {
64 name: path.file_name().unwrap().to_string_lossy().to_string(),
65 path,
66 content,
67 services: Vec::new(),
68 });
69 }
70 }
71
72 if files.is_empty() {
73 Ok(None)
74 } else {
75 Ok(Some(CachedProto {
76 files,
77 fingerprint: Fingerprint {
78 algorithm: "sha256".to_string(),
79 value: "cached".to_string(),
80 },
81 cached_at: std::time::SystemTime::now(),
82 expires_at: None,
83 }))
84 }
85 }
86
87 async fn cache_proto(&self, service_name: &str, files: &[ProtoFile]) -> Result<()> {
88 let cache_path = self.get_service_proto_dir(service_name);
89 std::fs::create_dir_all(&cache_path)?;
90
91 for file in files {
92 let file_name = if file.name.ends_with(".proto") {
94 file.name.clone()
95 } else {
96 format!("{}.proto", file.name)
97 };
98 let file_path = cache_path.join(&file_name);
99 std::fs::write(&file_path, &file.content)?;
100 tracing::debug!(
101 "Cached proto file: {} -> {}",
102 file.name,
103 file_path.display()
104 );
105 }
106
107 tracing::info!(
108 "Cached {} proto files to protos/remote/{}/",
109 files.len(),
110 service_name
111 );
112 Ok(())
113 }
114
115 async fn invalidate_cache(&self, service_name: &str) -> Result<()> {
116 let cache_path = self.get_service_proto_dir(service_name);
117 if cache_path.exists() {
118 std::fs::remove_dir_all(&cache_path)?;
119 }
120 Ok(())
121 }
122
123 async fn clear_cache(&self) -> Result<()> {
124 let proto_dir = self.project_root.join("protos");
125 if proto_dir.exists() {
126 std::fs::remove_dir_all(&proto_dir)?;
127 }
128 Ok(())
129 }
130
131 async fn get_cache_stats(&self) -> Result<CacheStats> {
132 let proto_dir = self.project_root.join("protos");
133 let mut total_size = 0u64;
134 let mut entry_count = 0usize;
135
136 if proto_dir.exists() {
137 for entry in std::fs::read_dir(&proto_dir)? {
138 entry_count += 1;
139 let entry = entry?;
140 if entry.path().is_dir() {
141 for file in std::fs::read_dir(entry.path())? {
142 let file = file?;
143 total_size += file.metadata()?.len();
144 }
145 }
146 }
147 }
148
149 Ok(CacheStats {
150 total_entries: entry_count,
151 total_size_bytes: total_size,
152 hit_rate: 0.0,
153 miss_rate: 0.0,
154 })
155 }
156}