aster/memory/
memory_manager.rs1use std::collections::HashMap;
6use std::fs;
7use std::path::{Path, PathBuf};
8
9use chrono::Utc;
10
11use super::types::{MemoryEntry, MemoryScope, SimpleMemoryStore, Timestamp};
12
13const MEMORY_VERSION: &str = "1.0.0";
14
15fn now() -> Timestamp {
17 Utc::now().to_rfc3339()
18}
19
20pub struct MemoryManager {
22 global_store_path: PathBuf,
23 project_store_path: PathBuf,
24 global_store: SimpleMemoryStore,
25 project_store: SimpleMemoryStore,
26}
27
28impl MemoryManager {
29 pub fn new(project_dir: Option<&Path>) -> Self {
31 let global_dir = dirs::home_dir()
32 .unwrap_or_default()
33 .join(".aster")
34 .join("memory");
35
36 let project_dir_path = project_dir
37 .map(|p| p.join(".aster").join("memory"))
38 .unwrap_or_else(|| {
39 std::env::current_dir()
40 .unwrap_or_default()
41 .join(".aster")
42 .join("memory")
43 });
44
45 let global_store_path = global_dir.join("memory.json");
46 let project_store_path = project_dir_path.join("memory.json");
47
48 let global_store = Self::load_store(&global_store_path);
49 let project_store = Self::load_store(&project_store_path);
50
51 Self {
52 global_store_path,
53 project_store_path,
54 global_store,
55 project_store,
56 }
57 }
58
59 pub fn set(&mut self, key: &str, value: &str, scope: MemoryScope) {
61 let (store, store_path) = match scope {
62 MemoryScope::Global => (&mut self.global_store, &self.global_store_path),
63 MemoryScope::Project => (&mut self.project_store, &self.project_store_path),
64 };
65
66 let current_time = now();
67 let existing = store.entries.get(key);
68
69 let entry = MemoryEntry {
70 key: key.to_string(),
71 value: value.to_string(),
72 scope,
73 created_at: existing
74 .map(|e| e.created_at.clone())
75 .unwrap_or_else(|| current_time.clone()),
76 updated_at: current_time,
77 };
78
79 store.entries.insert(key.to_string(), entry);
80 Self::save_store(store_path, store);
81 }
82
83 pub fn get(&self, key: &str, scope: Option<MemoryScope>) -> Option<&str> {
85 match scope {
86 Some(MemoryScope::Global) => {
87 self.global_store.entries.get(key).map(|e| e.value.as_str())
88 }
89 Some(MemoryScope::Project) => self
90 .project_store
91 .entries
92 .get(key)
93 .map(|e| e.value.as_str()),
94 None => {
95 self.project_store
97 .entries
98 .get(key)
99 .or_else(|| self.global_store.entries.get(key))
100 .map(|e| e.value.as_str())
101 }
102 }
103 }
104
105 pub fn delete(&mut self, key: &str, scope: MemoryScope) -> bool {
107 let (store, store_path) = match scope {
108 MemoryScope::Global => (&mut self.global_store, &self.global_store_path),
109 MemoryScope::Project => (&mut self.project_store, &self.project_store_path),
110 };
111
112 if store.entries.remove(key).is_some() {
113 Self::save_store(store_path, store);
114 true
115 } else {
116 false
117 }
118 }
119
120 pub fn list(&self, scope: Option<MemoryScope>) -> Vec<&MemoryEntry> {
122 let mut entries: Vec<&MemoryEntry> = Vec::new();
123
124 if scope != Some(MemoryScope::Project) {
125 entries.extend(self.global_store.entries.values());
126 }
127 if scope != Some(MemoryScope::Global) {
128 entries.extend(self.project_store.entries.values());
129 }
130
131 entries.sort_by(|a, b| b.updated_at.cmp(&a.updated_at));
132 entries
133 }
134
135 pub fn clear(&mut self, scope: MemoryScope) {
137 let (store, store_path) = match scope {
138 MemoryScope::Global => (&mut self.global_store, &self.global_store_path),
139 MemoryScope::Project => (&mut self.project_store, &self.project_store_path),
140 };
141
142 store.entries.clear();
143 Self::save_store(store_path, store);
144 }
145
146 pub fn get_summary(&self) -> String {
148 let entries = self.list(None);
149 if entries.is_empty() {
150 return String::new();
151 }
152
153 let lines: Vec<String> = entries
154 .iter()
155 .take(20)
156 .map(|e| format!("- {}: {}", e.key, e.value))
157 .collect();
158
159 format!("User Memory:\n{}", lines.join("\n"))
160 }
161
162 pub fn search(&self, query: &str) -> Vec<&MemoryEntry> {
164 let entries = self.list(None);
165 let lower_query = query.to_lowercase();
166
167 entries
168 .into_iter()
169 .filter(|e| {
170 e.key.to_lowercase().contains(&lower_query)
171 || e.value.to_lowercase().contains(&lower_query)
172 })
173 .collect()
174 }
175
176 fn load_store(path: &Path) -> SimpleMemoryStore {
179 if path.exists() {
180 if let Ok(content) = fs::read_to_string(path) {
181 if let Ok(store) = serde_json::from_str(&content) {
182 return store;
183 }
184 }
185 }
186 SimpleMemoryStore {
187 entries: HashMap::new(),
188 version: MEMORY_VERSION.to_string(),
189 }
190 }
191
192 fn save_store(path: &Path, store: &SimpleMemoryStore) {
193 if let Some(parent) = path.parent() {
194 let _ = fs::create_dir_all(parent);
195 }
196 if let Ok(content) = serde_json::to_string_pretty(store) {
197 let _ = fs::write(path, content);
198 }
199 }
200}
201
202impl Default for MemoryManager {
203 fn default() -> Self {
204 Self::new(None)
205 }
206}