diff options
author | Vika <vika@fireburn.ru> | 2023-07-09 01:28:00 +0300 |
---|---|---|
committer | Vika <vika@fireburn.ru> | 2023-07-09 01:28:00 +0300 |
commit | 2156ed8d0ab62e677037b104c08489e5eed55733 (patch) | |
tree | 2fc3961421a9573877c2f91129f9c52b9f7e9fd0 /kittybox-rs | |
parent | dd10254f36409df57d7cd9ab30e7af139121a428 (diff) | |
download | kittybox-2156ed8d0ab62e677037b104c08489e5eed55733.tar.zst |
database/memory: cleaner update_post implementation
This one manages to avoid extraneous allocations as much as possible, by deconstructing the update into pieces and using a mutable reference taken directly from the hashmap in which the posts are stored. Now if only this hashmap were to be serialized on Drop, we could even have persistence in the database and therefore gain another backend that requires no dependencies to run, just like FileStorage, but avoids extraneous file access (or maybe shunts it into the background?)
Diffstat (limited to 'kittybox-rs')
-rw-r--r-- | kittybox-rs/src/database/memory.rs | 71 |
1 files changed, 70 insertions, 1 deletions
diff --git a/kittybox-rs/src/database/memory.rs b/kittybox-rs/src/database/memory.rs index ce98d05..26d3095 100644 --- a/kittybox-rs/src/database/memory.rs +++ b/kittybox-rs/src/database/memory.rs @@ -89,7 +89,76 @@ impl Storage for MemoryStorage { } async fn update_post(&self, url: &'_ str, update: crate::micropub::MicropubUpdate) -> Result<()> { - todo!() + let mut guard = self.mapping.write().await; + let mut post = guard.get_mut(url).ok_or(StorageError::from_static(ErrorKind::NotFound, "The specified post wasn't found in the database."))?; + + use crate::micropub::MicropubPropertyDeletion; + + let mut add_keys: HashMap<String, Vec<serde_json::Value>> = HashMap::new(); + let mut remove_keys: Vec<String> = vec![]; + let mut remove_values: HashMap<String, Vec<serde_json::Value>> = HashMap::new(); + + if let Some(MicropubPropertyDeletion::Properties(delete)) = update.delete { + remove_keys.extend(delete.iter().cloned()); + } else if let Some(MicropubPropertyDeletion::Values(delete)) = update.delete { + for (k, v) in delete { + remove_values + .entry(k.to_string()) + .or_default() + .extend(v.clone()); + } + } + if let Some(add) = update.add { + for (k, v) in add { + add_keys.insert(k.to_string(), v.clone()); + } + } + if let Some(replace) = update.replace { + for (k, v) in replace { + remove_keys.push(k.to_string()); + add_keys.insert(k.to_string(), v.clone()); + } + } + + if let Some(props) = post["properties"].as_object_mut() { + for k in remove_keys { + props.remove(&k); + } + } + for (k, v) in remove_values { + let k = &k; + let props = if k == "children" { + &mut post + } else { + &mut post["properties"] + }; + v.iter().for_each(|v| { + if let Some(vec) = props[k].as_array_mut() { + if let Some(index) = vec.iter().position(|w| w == v) { + vec.remove(index); + } + } + }); + } + for (k, v) in add_keys { + tracing::debug!("Adding k/v to post: {} => {:?}", k, v); + let props = if k == "children" { + &mut post + } else { + &mut post["properties"] + }; + if let Some(prop) = props[&k].as_array_mut() { + if k == "children" { + v.into_iter().rev().for_each(|v| prop.insert(0, v)); + } else { + prop.extend(v.into_iter()); + } + } else { + props[&k] = serde_json::Value::Array(v) + } + } + + Ok(()) } async fn get_channels(&self, user: &'_ str) -> Result<Vec<MicropubChannel>> { |