Skip to main content

otherone_memory/
storage.rs

1use 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}