otherone_memory/
storage.rs1use std::fs;
2use std::path::PathBuf;
3use std::sync::{OnceLock, RwLock};
4
5use serde::{Deserialize, Serialize};
6
7use crate::error::MemoryError;
8use crate::tree::MemoryTree;
9use crate::types::MemoryPoint;
10
11static MEMORY_STORAGE_ROOT: OnceLock<RwLock<Option<PathBuf>>> = OnceLock::new();
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct MemoryStorageFile {
15 #[serde(default)]
16 pub points: Vec<MemoryPoint>,
17}
18
19pub fn set_memory_storage_root(root: impl Into<PathBuf>) {
20 let lock = MEMORY_STORAGE_ROOT.get_or_init(|| RwLock::new(None));
21 if let Ok(mut configured_root) = lock.write() {
22 *configured_root = Some(root.into());
23 }
24}
25
26pub fn clear_memory_storage_root() {
27 let lock = MEMORY_STORAGE_ROOT.get_or_init(|| RwLock::new(None));
28 if let Ok(mut configured_root) = lock.write() {
29 *configured_root = None;
30 }
31}
32
33pub fn memory_storage_path() -> PathBuf {
34 memory_storage_root()
35 .join(".otherone")
36 .join("memory")
37 .join("long-term-memory.json")
38}
39
40pub fn read_memory_tree() -> Result<MemoryTree, MemoryError> {
41 let path = memory_storage_path();
42
43 if !path.exists() {
44 let tree = MemoryTree::new();
45 write_memory_tree(&tree)?;
46 return Ok(tree);
47 }
48
49 let content =
50 fs::read_to_string(&path).map_err(|error| MemoryError::IoError(error.to_string()))?;
51 let storage_file: MemoryStorageFile = serde_json::from_str(&content)
52 .map_err(|error| MemoryError::JsonError(error.to_string()))?;
53
54 MemoryTree::from_points(storage_file.points)
55}
56
57pub fn write_memory_tree(tree: &MemoryTree) -> Result<(), MemoryError> {
58 let path = memory_storage_path();
59 if let Some(parent) = path.parent() {
60 fs::create_dir_all(parent).map_err(|error| MemoryError::IoError(error.to_string()))?;
61 }
62
63 let storage_file = MemoryStorageFile {
64 points: tree.to_points(),
65 };
66 let content = serde_json::to_string_pretty(&storage_file)
67 .map_err(|error| MemoryError::JsonError(error.to_string()))?;
68
69 fs::write(&path, content).map_err(|error| MemoryError::IoError(error.to_string()))?;
70 Ok(())
71}
72
73fn memory_storage_root() -> PathBuf {
74 MEMORY_STORAGE_ROOT
75 .get_or_init(|| RwLock::new(None))
76 .read()
77 .ok()
78 .and_then(|configured_root| configured_root.clone())
79 .unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")))
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85
86 #[test]
87 fn reads_and_writes_memory_tree_file() {
88 let root = std::env::temp_dir().join(format!(
89 "otherone-memory-storage-{}",
90 std::time::SystemTime::now()
91 .duration_since(std::time::UNIX_EPOCH)
92 .unwrap()
93 .as_nanos()
94 ));
95 set_memory_storage_root(root.clone());
96
97 let mut tree = MemoryTree::new();
98 let root_id = tree
99 .insert_root("likes noodles", "food preference")
100 .unwrap();
101 write_memory_tree(&tree).unwrap();
102
103 let restored = read_memory_tree().unwrap();
104 assert!(memory_storage_path().starts_with(&root));
105 assert!(restored.contains(&root_id));
106
107 clear_memory_storage_root();
108 }
109}