bamboo_memory/memory_store/
paths.rs1use std::path::{Path, PathBuf};
2
3use bamboo_infrastructure::paths::bamboo_dir;
4
5use super::types::MemoryScope;
6
7pub const MEMORY_ROOT_DIR: &str = "memory";
8pub const MEMORY_VERSION_DIR: &str = "v1";
9pub const SESSIONS_DIR: &str = "sessions";
10pub const SCOPES_DIR: &str = "scopes";
11pub const GLOBAL_DIR: &str = "global";
12pub const PROJECTS_DIR: &str = "projects";
13pub const NOTE_DIR: &str = "note";
14pub const STATE_DIR: &str = "state";
15pub const INDEXES_DIR: &str = "indexes";
16pub const VIEWS_DIR: &str = "views";
17pub const LOGS_DIR: &str = "logs";
18pub const LOCKS_DIR: &str = "locks";
19pub const TOPICS_DIR: &str = "topics";
20
21#[derive(Debug, Clone)]
22pub struct MemoryPathResolver {
23 data_dir: PathBuf,
24 root: PathBuf,
25}
26
27impl Default for MemoryPathResolver {
28 fn default() -> Self {
29 Self::from_data_dir(bamboo_dir())
30 }
31}
32
33impl MemoryPathResolver {
34 pub fn new(root: impl Into<PathBuf>) -> Self {
35 let root = root.into();
36 let data_dir = infer_data_dir_from_root(&root);
37 Self { data_dir, root }
38 }
39
40 pub fn from_data_dir(data_dir: impl Into<PathBuf>) -> Self {
41 let data_dir = data_dir.into();
42 let root = data_dir.join(MEMORY_ROOT_DIR).join(MEMORY_VERSION_DIR);
43 Self { data_dir, root }
44 }
45
46 pub fn data_dir(&self) -> PathBuf {
47 self.data_dir.clone()
48 }
49
50 pub fn root(&self) -> PathBuf {
51 self.root.clone()
52 }
53
54 pub fn sessions_root(&self) -> PathBuf {
55 self.root.join(SESSIONS_DIR)
56 }
57
58 pub fn session_root(&self, session_id: &str) -> PathBuf {
59 self.sessions_root().join(session_id)
60 }
61
62 pub fn session_note_dir(&self, session_id: &str) -> PathBuf {
63 self.session_root(session_id).join(NOTE_DIR)
64 }
65
66 pub fn session_topic_path(&self, session_id: &str, topic: &str) -> PathBuf {
67 self.session_note_dir(session_id)
68 .join(format!("{}.md", topic))
69 }
70
71 pub fn session_state_path(&self, session_id: &str) -> PathBuf {
72 self.session_root(session_id).join("state.json")
73 }
74
75 pub fn scopes_root(&self) -> PathBuf {
76 self.root.join(SCOPES_DIR)
77 }
78
79 pub fn global_root(&self) -> PathBuf {
80 self.scopes_root().join(GLOBAL_DIR)
81 }
82
83 pub fn project_root(&self, project_key: &str) -> PathBuf {
84 self.scopes_root().join(PROJECTS_DIR).join(project_key)
85 }
86
87 pub fn scope_root(&self, scope: MemoryScope, project_key: Option<&str>) -> PathBuf {
88 match scope {
89 MemoryScope::Global => self.global_root(),
90 MemoryScope::Project => self.project_root(project_key.unwrap_or("unknown")),
91 MemoryScope::Session => self.sessions_root(),
92 }
93 }
94
95 pub fn topic_dir(&self, scope: MemoryScope, project_key: Option<&str>) -> PathBuf {
96 match scope {
97 MemoryScope::Global => self.global_root().join(TOPICS_DIR),
98 MemoryScope::Project => self
99 .project_root(project_key.unwrap_or("unknown"))
100 .join(TOPICS_DIR),
101 MemoryScope::Session => self.sessions_root(),
102 }
103 }
104
105 pub fn topic_path(
106 &self,
107 scope: MemoryScope,
108 project_key: Option<&str>,
109 memory_id: &str,
110 ) -> PathBuf {
111 self.topic_dir(scope, project_key)
112 .join(format!("{}.md", memory_id))
113 }
114
115 pub fn indexes_dir(&self, scope: MemoryScope, project_key: Option<&str>) -> PathBuf {
116 match scope {
117 MemoryScope::Global => self.global_root().join(INDEXES_DIR),
118 MemoryScope::Project => self
119 .project_root(project_key.unwrap_or("unknown"))
120 .join(INDEXES_DIR),
121 MemoryScope::Session => self.sessions_root(),
122 }
123 }
124
125 pub fn views_dir(&self, scope: MemoryScope, project_key: Option<&str>) -> PathBuf {
126 match scope {
127 MemoryScope::Global => self.global_root().join(VIEWS_DIR),
128 MemoryScope::Project => self
129 .project_root(project_key.unwrap_or("unknown"))
130 .join(VIEWS_DIR),
131 MemoryScope::Session => self.sessions_root(),
132 }
133 }
134
135 pub fn logs_dir(&self, scope: MemoryScope, project_key: Option<&str>) -> PathBuf {
136 match scope {
137 MemoryScope::Global => self.global_root().join(LOGS_DIR),
138 MemoryScope::Project => self
139 .project_root(project_key.unwrap_or("unknown"))
140 .join(LOGS_DIR),
141 MemoryScope::Session => self.sessions_root(),
142 }
143 }
144
145 pub fn state_dir(&self, scope: MemoryScope, project_key: Option<&str>) -> PathBuf {
146 match scope {
147 MemoryScope::Global => self.global_root().join(STATE_DIR),
148 MemoryScope::Project => self
149 .project_root(project_key.unwrap_or("unknown"))
150 .join(STATE_DIR),
151 MemoryScope::Session => self.sessions_root(),
152 }
153 }
154
155 pub fn locks_dir(&self, scope: MemoryScope, project_key: Option<&str>) -> PathBuf {
156 self.state_dir(scope, project_key).join(LOCKS_DIR)
157 }
158
159 pub fn legacy_notes_root(&self) -> PathBuf {
160 self.data_dir.join("notes")
161 }
162
163 pub fn legacy_session_file(&self, session_id: &str) -> PathBuf {
164 self.legacy_notes_root().join(format!("{}.md", session_id))
165 }
166
167 pub fn legacy_session_topic_dir(&self, session_id: &str) -> PathBuf {
168 self.legacy_notes_root().join(session_id)
169 }
170}
171
172pub fn memory_root() -> PathBuf {
173 MemoryPathResolver::from_data_dir(bamboo_dir()).root()
174}
175
176fn infer_data_dir_from_root(root: &Path) -> PathBuf {
177 if root
178 .file_name()
179 .and_then(|value| value.to_str())
180 .is_some_and(|value| value == MEMORY_VERSION_DIR)
181 {
182 if let Some(memory_dir) = root.parent() {
183 if memory_dir
184 .file_name()
185 .and_then(|value| value.to_str())
186 .is_some_and(|value| value == MEMORY_ROOT_DIR)
187 {
188 if let Some(data_dir) = memory_dir.parent() {
189 return data_dir.to_path_buf();
190 }
191 }
192 }
193 }
194 root.to_path_buf()
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200
201 #[test]
202 fn paths_follow_v1_layout() {
203 let resolver = MemoryPathResolver::new("/tmp/memory/v1");
204 assert_eq!(
205 resolver.session_topic_path("session-1", "default"),
206 PathBuf::from("/tmp/memory/v1/sessions/session-1/note/default.md")
207 );
208 assert_eq!(
209 resolver.topic_path(MemoryScope::Project, Some("proj-1"), "mem_1"),
210 PathBuf::from("/tmp/memory/v1/scopes/projects/proj-1/topics/mem_1.md")
211 );
212 }
213}