1use std::{
7 collections::{BTreeMap, BTreeSet},
8 sync::{Arc, Mutex},
9};
10
11use sim_kernel::{
12 Cx, Error, Expr, Object, ObjectEncode, ObjectEncoding, Result, Symbol, Value,
13 id::CORE_TABLE_CLASS_ID,
14 object::ClassRef,
15 table::{Dir, Table},
16};
17
18use crate::{
19 citizen::db_dir_class_symbol, table_db_capability, table_db_mkdir_capability,
20 table_db_read_capability, table_db_rmdir_capability, table_db_write_capability,
21};
22
23struct Store {
24 values: BTreeMap<(String, Symbol), Value>,
25 dirs: BTreeSet<String>,
26}
27
28#[derive(Clone)]
41pub struct DbDir {
42 store: Arc<Mutex<Store>>,
43 path: String,
44}
45
46impl DbDir {
47 pub fn open() -> Self {
50 let mut dirs = BTreeSet::new();
51 dirs.insert(String::new());
52 Self {
53 store: Arc::new(Mutex::new(Store {
54 values: BTreeMap::new(),
55 dirs,
56 })),
57 path: String::new(),
58 }
59 }
60
61 fn with_store(store: Arc<Mutex<Store>>, path: String) -> Self {
62 Self { store, path }
63 }
64
65 fn lock(&self) -> Result<std::sync::MutexGuard<'_, Store>> {
66 self.store
67 .lock()
68 .map_err(|_| Error::Eval("table/db lock poisoned".into()))
69 }
70
71 fn child_path(&self, name: &Symbol) -> Result<String> {
72 let segment = name.name.as_ref();
73 if !sim_table_core::is_legal_table_segment(segment) {
74 return Err(Error::Eval(format!("table/db: illegal name {segment:?}")));
75 }
76 Ok(if self.path.is_empty() {
77 segment.to_owned()
78 } else {
79 format!("{}/{segment}", self.path)
80 })
81 }
82
83 fn direct_subdirs(&self, store: &Store) -> Vec<Symbol> {
84 let prefix = if self.path.is_empty() {
85 None
86 } else {
87 Some(format!("{}/", self.path))
88 };
89 let mut names = BTreeSet::new();
90 for path in &store.dirs {
91 if path.is_empty() || *path == self.path {
92 continue;
93 }
94 let Some(rest) = prefix
95 .as_ref()
96 .map_or_else(|| Some(path.as_str()), |prefix| path.strip_prefix(prefix))
97 else {
98 continue;
99 };
100 if let Some((head, tail)) = rest.split_once('/') {
101 if !head.is_empty() && !tail.is_empty() {
102 names.insert(Symbol::new(head));
103 }
104 } else if !rest.is_empty() {
105 names.insert(Symbol::new(rest));
106 }
107 }
108 names.into_iter().collect()
109 }
110
111 fn descriptor_path(&self) -> Vec<String> {
112 if self.path.is_empty() {
113 Vec::new()
114 } else {
115 self.path
116 .split('/')
117 .map(std::borrow::ToOwned::to_owned)
118 .collect()
119 }
120 }
121}
122
123impl Default for DbDir {
124 fn default() -> Self {
125 Self::open()
126 }
127}
128
129impl Object for DbDir {
130 fn display(&self, _cx: &mut Cx) -> Result<String> {
131 if self.path.is_empty() {
132 Ok("table/db[/]".to_owned())
133 } else {
134 Ok(format!("table/db[/{}]", self.path))
135 }
136 }
137
138 fn as_any(&self) -> &dyn std::any::Any {
139 self
140 }
141}
142
143impl sim_kernel::ObjectCompat for DbDir {
144 fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
145 let symbol = db_dir_class_symbol();
146 if let Some(value) = cx.registry().class_by_symbol(&symbol) {
147 return Ok(value.clone());
148 }
149 let symbol = Symbol::qualified("core", "Table");
150 if let Some(value) = cx.registry().class_by_symbol(&symbol) {
151 return Ok(value.clone());
152 }
153 cx.factory().class_stub(CORE_TABLE_CLASS_ID, symbol)
154 }
155 fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
156 self.as_table_expr(cx)
157 }
158 fn truth(&self, cx: &mut Cx) -> Result<bool> {
159 Ok(!self.is_empty(cx)?)
160 }
161 fn as_table_impl(&self) -> Option<&dyn Table> {
162 Some(self)
163 }
164 fn as_dir(&self) -> Option<&dyn Dir> {
165 Some(self)
166 }
167 fn as_object_encoder(&self) -> Option<&dyn ObjectEncode> {
168 Some(self)
169 }
170}
171
172impl ObjectEncode for DbDir {
173 fn object_encoding(&self, _cx: &mut Cx) -> Result<ObjectEncoding> {
174 Ok(ObjectEncoding::Constructor {
175 class: db_dir_class_symbol(),
176 args: vec![
177 Expr::Symbol(Symbol::new("v0")),
178 sim_table_core::citizen_fields::path_segments::encode(&self.descriptor_path()),
179 ],
180 })
181 }
182}
183
184impl sim_citizen::Citizen for DbDir {
185 fn citizen_symbol() -> Symbol {
186 db_dir_class_symbol()
187 }
188
189 fn citizen_version() -> u32 {
190 0
191 }
192
193 fn citizen_arity() -> usize {
194 1
195 }
196
197 fn citizen_fields() -> &'static [&'static str] {
198 &["path"]
199 }
200}
201
202impl Table for DbDir {
203 fn backend_symbol(&self) -> Symbol {
204 Symbol::qualified("table", "db")
205 }
206
207 fn get(&self, cx: &mut Cx, key: Symbol) -> Result<Value> {
208 cx.require(&table_db_read_capability())?;
209 let value = self.lock()?.values.get(&(self.path.clone(), key)).cloned();
210 match value {
211 Some(value) => Ok(value),
212 None => cx.factory().nil(),
213 }
214 }
215
216 fn set(&self, cx: &mut Cx, key: Symbol, value: Value) -> Result<()> {
217 cx.require(&table_db_write_capability())?;
218 let path = self.child_path(&key)?;
219 let mut store = self.lock()?;
220 if store.dirs.contains(&path) {
221 return Err(Error::Eval(format!("table/db: {key} is a directory")));
222 }
223 store.values.insert((self.path.clone(), key), value);
224 Ok(())
225 }
226
227 fn has(&self, cx: &mut Cx, key: Symbol) -> Result<bool> {
228 cx.require(&table_db_read_capability())?;
229 let path = self.child_path(&key)?;
230 let store = self.lock()?;
231 Ok(store.values.contains_key(&(self.path.clone(), key)) || store.dirs.contains(&path))
232 }
233
234 fn del(&self, cx: &mut Cx, key: Symbol) -> Result<Value> {
235 cx.require(&table_db_write_capability())?;
236 let value = self.lock()?.values.remove(&(self.path.clone(), key));
237 match value {
238 Some(value) => Ok(value),
239 None => cx.factory().nil(),
240 }
241 }
242
243 fn keys(&self, cx: &mut Cx) -> Result<Vec<Symbol>> {
244 cx.require(&table_db_read_capability())?;
245 let store = self.lock()?;
246 let mut keys = BTreeSet::new();
247 for (path, key) in store.values.keys() {
248 if *path == self.path {
249 keys.insert(key.clone());
250 }
251 }
252 for key in self.direct_subdirs(&store) {
253 keys.insert(key);
254 }
255 Ok(keys.into_iter().collect())
256 }
257
258 fn entries(&self, cx: &mut Cx) -> Result<Vec<(Symbol, Value)>> {
259 cx.require(&table_db_read_capability())?;
260 let store = self.lock()?;
261 Ok(store
262 .values
263 .iter()
264 .filter(|((path, _), _)| *path == self.path)
265 .map(|((_, key), value)| (key.clone(), value.clone()))
266 .collect())
267 }
268
269 fn len(&self, cx: &mut Cx) -> Result<usize> {
270 Ok(self.entries(cx)?.len())
271 }
272
273 fn clear(&self, cx: &mut Cx) -> Result<()> {
274 cx.require(&table_db_write_capability())?;
275 self.lock()?
276 .values
277 .retain(|(path, _), _| *path != self.path);
278 Ok(())
279 }
280}
281
282impl Dir for DbDir {
283 fn mkdir(&self, cx: &mut Cx, name: Symbol) -> Result<Value> {
284 cx.require(&table_db_mkdir_capability())?;
285 let path = self.child_path(&name)?;
286 let mut store = self.lock()?;
287 if store
288 .values
289 .contains_key(&(self.path.clone(), name.clone()))
290 {
291 return Err(Error::Eval(format!("table/db: {name} is a file")));
292 }
293 store.dirs.insert(path.clone());
294 cx.factory()
295 .opaque(Arc::new(Self::with_store(self.store.clone(), path)))
296 }
297
298 fn opendir(&self, cx: &mut Cx, name: Symbol) -> Result<Option<Value>> {
299 cx.require(&table_db_read_capability())?;
300 let path = self.child_path(&name)?;
301 let store = self.lock()?;
302 if store.dirs.contains(&path) {
303 return Ok(Some(
304 cx.factory()
305 .opaque(Arc::new(Self::with_store(self.store.clone(), path)))?,
306 ));
307 }
308 if store
309 .values
310 .contains_key(&(self.path.clone(), name.clone()))
311 {
312 return Err(Error::Eval(format!("table/db: {name} is not a directory")));
313 }
314 Ok(None)
315 }
316
317 fn rmdir(&self, cx: &mut Cx, name: Symbol) -> Result<Value> {
318 cx.require(&table_db_rmdir_capability())?;
319 let path = self.child_path(&name)?;
320 let mut store = self.lock()?;
321 if !store.dirs.contains(&path) {
322 return Err(Error::Eval(format!("table/db: {name} is not a directory")));
323 }
324 let prefix = format!("{path}/");
325 store
326 .values
327 .retain(|(entry_path, _), _| *entry_path != path && !entry_path.starts_with(&prefix));
328 store
329 .dirs
330 .retain(|dir_path| *dir_path != path && !dir_path.starts_with(&prefix));
331 cx.factory().nil()
332 }
333
334 fn is_dir(&self, cx: &mut Cx, name: Symbol) -> Result<bool> {
335 cx.require(&table_db_read_capability())?;
336 let path = self.child_path(&name)?;
337 Ok(self.lock()?.dirs.contains(&path))
338 }
339}
340
341pub fn install_db_dir_lib(cx: &mut Cx) -> Result<Value> {
372 cx.require(&table_db_capability())?;
373 cx.factory().opaque(Arc::new(DbDir::open()))
374}