ceylon_next/memory/
file_store.rs

1//! File-based persistent memory implementation.
2//!
3//! This module provides file-based storage with support for both JSON
4//! (human-readable) and MessagePack (compact binary) formats.
5
6use super::{Memory, MemoryEntry};
7use async_trait::async_trait;
8use std::collections::HashMap;
9use std::fs;
10use std::io::{Read, Write};
11use std::path::PathBuf;
12use std::sync::{Arc, RwLock};
13
14/// Serialization format for file-based storage
15#[derive(Debug, Clone, Copy)]
16pub enum StorageFormat {
17    /// JSON format (human-readable)
18    Json,
19    /// MessagePack format (compact binary)
20    MessagePack,
21}
22
23/// A file-based implementation of the [`Memory`] trait.
24///
25/// This provides persistent storage of conversation history using files.
26/// Supports both JSON (human-readable) and MessagePack (compact binary) formats.
27///
28/// # Examples
29///
30/// ```rust,no_run
31/// use ceylon_next::memory::{FileStore, StorageFormat};
32/// use std::sync::Arc;
33///
34/// #[tokio::main]
35/// async fn main() {
36///     // JSON format
37///     let memory = Arc::new(
38///         FileStore::new("memory.json", StorageFormat::Json).await.unwrap()
39///     );
40///
41///     // MessagePack format
42///     let memory_mp = Arc::new(
43///         FileStore::new("memory.msgpack", StorageFormat::MessagePack).await.unwrap()
44///     );
45/// }
46/// ```
47pub struct FileStore {
48    file_path: PathBuf,
49    format: StorageFormat,
50    entries: Arc<RwLock<HashMap<String, MemoryEntry>>>,
51    agent_index: Arc<RwLock<HashMap<String, Vec<String>>>>,
52}
53
54impl FileStore {
55    /// Creates a new file-based store with the given file path and format.
56    ///
57    /// # Arguments
58    ///
59    /// * `file_path` - Path to the storage file
60    /// * `format` - Serialization format (JSON or MessagePack)
61    ///
62    /// # Examples
63    ///
64    /// ```rust,no_run
65    /// use ceylon_next::memory::{FileStore, StorageFormat};
66    ///
67    /// #[tokio::main]
68    /// async fn main() {
69    ///     let store = FileStore::new("memory.json", StorageFormat::Json).await.unwrap();
70    /// }
71    /// ```
72    pub async fn new<P: Into<PathBuf>>(file_path: P, format: StorageFormat) -> Result<Self, String> {
73        let file_path = file_path.into();
74        let mut entries: HashMap<String, MemoryEntry> = HashMap::new();
75        let mut agent_index: HashMap<String, Vec<String>> = HashMap::new();
76
77        // Load existing data if file exists
78        if file_path.exists() {
79            let mut file = fs::File::open(&file_path)
80                .map_err(|e| format!("Failed to open file: {}", e))?;
81
82            let mut buffer = Vec::new();
83            file.read_to_end(&mut buffer)
84                .map_err(|e| format!("Failed to read file: {}", e))?;
85
86            if !buffer.is_empty() {
87                entries = match format {
88                    StorageFormat::Json => serde_json::from_slice(&buffer)
89                        .map_err(|e| format!("Failed to deserialize JSON: {}", e))?,
90                    StorageFormat::MessagePack => rmp_serde::from_slice(&buffer)
91                        .map_err(|e| format!("Failed to deserialize MessagePack: {}", e))?,
92                };
93
94                // Rebuild agent index
95                for (entry_id, entry) in &entries {
96                    agent_index
97                        .entry(entry.agent_id.clone())
98                        .or_insert_with(Vec::new)
99                        .push(entry_id.clone());
100                }
101            }
102        }
103
104        Ok(Self {
105            file_path,
106            format,
107            entries: Arc::new(RwLock::new(entries)),
108            agent_index: Arc::new(RwLock::new(agent_index)),
109        })
110    }
111
112    /// Returns the path to the storage file.
113    pub fn file_path(&self) -> &PathBuf {
114        &self.file_path
115    }
116
117    /// Returns the storage format being used.
118    pub fn format(&self) -> StorageFormat {
119        self.format
120    }
121
122    /// Persists the current in-memory data to disk.
123    fn save_to_disk(&self) -> Result<(), String> {
124        let entries = self.entries.read().unwrap();
125
126        let serialized = match self.format {
127            StorageFormat::Json => serde_json::to_vec_pretty(&*entries)
128                .map_err(|e| format!("Failed to serialize to JSON: {}", e))?,
129            StorageFormat::MessagePack => rmp_serde::to_vec(&*entries)
130                .map_err(|e| format!("Failed to serialize to MessagePack: {}", e))?,
131        };
132
133        let mut file = fs::File::create(&self.file_path)
134            .map_err(|e| format!("Failed to create file: {}", e))?;
135
136        file.write_all(&serialized)
137            .map_err(|e| format!("Failed to write to file: {}", e))?;
138
139        Ok(())
140    }
141}
142
143#[async_trait]
144impl Memory for FileStore {
145    async fn store(&self, entry: MemoryEntry) -> Result<String, String> {
146        let entry_id = entry.id.clone();
147        let agent_id = entry.agent_id.clone();
148
149        // Store the entry
150        {
151            let mut entries = self.entries.write().unwrap();
152            entries.insert(entry_id.clone(), entry);
153        }
154
155        // Update agent index
156        {
157            let mut index = self.agent_index.write().unwrap();
158            index
159                .entry(agent_id)
160                .or_insert_with(Vec::new)
161                .push(entry_id.clone());
162        }
163
164        // Persist to disk
165        self.save_to_disk()?;
166
167        Ok(entry_id)
168    }
169
170    async fn get(&self, id: &str) -> Result<Option<MemoryEntry>, String> {
171        let entries = self.entries.read().unwrap();
172        Ok(entries.get(id).cloned())
173    }
174
175    async fn get_agent_history(&self, agent_id: &str) -> Result<Vec<MemoryEntry>, String> {
176        let index = self.agent_index.read().unwrap();
177        let entries = self.entries.read().unwrap();
178
179        let entry_ids = index.get(agent_id);
180
181        if entry_ids.is_none() {
182            return Ok(Vec::new());
183        }
184
185        let mut result = Vec::new();
186        for entry_id in entry_ids.unwrap() {
187            if let Some(entry) = entries.get(entry_id) {
188                result.push(entry.clone());
189            }
190        }
191
192        // Sort by timestamp (newest first)
193        result.sort_by(|a, b| b.created_at.cmp(&a.created_at));
194
195        Ok(result)
196    }
197
198    async fn get_recent(&self, agent_id: &str, limit: usize) -> Result<Vec<MemoryEntry>, String> {
199        let mut history = self.get_agent_history(agent_id).await?;
200        history.truncate(limit);
201        Ok(history)
202    }
203
204    async fn search(&self, agent_id: &str, query: &str) -> Result<Vec<MemoryEntry>, String> {
205        let history = self.get_agent_history(agent_id).await?;
206        let query_lower = query.to_lowercase();
207
208        let results: Vec<MemoryEntry> = history
209            .into_iter()
210            .filter(|entry| {
211                entry
212                    .messages
213                    .iter()
214                    .any(|msg| msg.content.to_lowercase().contains(&query_lower))
215            })
216            .collect();
217
218        Ok(results)
219    }
220
221    async fn clear_agent_memory(&self, agent_id: &str) -> Result<(), String> {
222        let index = self.agent_index.read().unwrap();
223        let entry_ids = index.get(agent_id);
224
225        if let Some(ids) = entry_ids {
226            let mut entries = self.entries.write().unwrap();
227            for id in ids {
228                entries.remove(id);
229            }
230        }
231
232        let mut index = self.agent_index.write().unwrap();
233        index.remove(agent_id);
234
235        // Persist to disk
236        drop(index); // Release the lock before calling save_to_disk
237        self.save_to_disk()?;
238
239        Ok(())
240    }
241}