You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
164 lines
4.4 KiB
Rust
164 lines
4.4 KiB
Rust
use std::{
|
|
collections::{BTreeSet, hash_map, HashMap, HashSet},
|
|
iter::FusedIterator,
|
|
sync::Arc,
|
|
};
|
|
|
|
use chrono::{DateTime, Utc};
|
|
use libshire::strings::ShString22;
|
|
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
|
|
|
use crate::post::{Post, PostId};
|
|
|
|
#[derive(Clone)]
|
|
pub struct ConcurrentPostsStore {
|
|
inner: Arc<RwLock<PostsStore>>,
|
|
}
|
|
|
|
impl ConcurrentPostsStore {
|
|
pub fn new() -> Self {
|
|
Self { inner: Arc::new(RwLock::new(PostsStore::new())) }
|
|
}
|
|
|
|
pub async fn read(&self) -> RwLockReadGuard<'_, PostsStore> {
|
|
self.inner.read().await
|
|
}
|
|
|
|
pub fn write_blocking(&self) -> RwLockWriteGuard<'_, PostsStore> {
|
|
self.inner.blocking_write()
|
|
}
|
|
|
|
pub async fn get(&self, id: &str) -> Option<Arc<Post>> {
|
|
self.read().await.get(id).cloned()
|
|
}
|
|
}
|
|
|
|
impl Default for ConcurrentPostsStore {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
pub struct PostsStore {
|
|
posts: HashMap<PostId, Arc<Post>>,
|
|
created_ix: BTreeSet<CreatedIxEntry>,
|
|
tags_ix: HashMap<ShString22, HashSet<PostId>>,
|
|
}
|
|
|
|
// TODO: shrink the various collections on removal to deallocate unneeded space
|
|
|
|
impl PostsStore {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
posts: HashMap::new(),
|
|
created_ix: BTreeSet::new(),
|
|
tags_ix: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
pub fn get(&self, id: &str) -> Option<&Arc<Post>> {
|
|
self.posts.get(id)
|
|
}
|
|
|
|
pub fn insert(&mut self, post: Post) -> Option<Arc<Post>> {
|
|
let old_post = self.remove(post.id());
|
|
|
|
// Insert the post into each of the tag indexes.
|
|
for tag in post.tags() {
|
|
// First, get the existing `HashSet` for the tag, or create a new one if one does not
|
|
// already exist. Then, insert the post's ID into the `HashSet`.
|
|
match self.tags_ix.entry(tag.clone()) {
|
|
hash_map::Entry::Occupied(entry) => entry.into_mut(),
|
|
hash_map::Entry::Vacant(entry) => entry.insert(HashSet::new()),
|
|
}.insert(post.id().clone());
|
|
}
|
|
|
|
// Insert the post into the correct position of the created BTree index.
|
|
self.created_ix.insert(CreatedIxEntry::new(&post));
|
|
|
|
// Wrap the post with an atomic reference counter and insert it into the main posts
|
|
// `HashMap`.
|
|
self.posts.insert(post.id().clone(), Arc::new(post));
|
|
|
|
old_post
|
|
}
|
|
|
|
pub fn remove(&mut self, id: &str) -> Option<Arc<Post>> {
|
|
match self.posts.remove(id) {
|
|
Some(post) => {
|
|
// Remove the post's entry in the created index.
|
|
self.created_ix
|
|
.remove(&CreatedIxEntry::new(&post));
|
|
|
|
// Remove every occurence of the post from the tags index.
|
|
for tag in post.tags() {
|
|
if let Some(tag_ix) = self.tags_ix.get_mut(tag) {
|
|
tag_ix.remove(id);
|
|
}
|
|
}
|
|
|
|
Some(post)
|
|
},
|
|
None => None,
|
|
}
|
|
}
|
|
|
|
pub fn clear(&mut self) {
|
|
self.tags_ix.clear();
|
|
self.created_ix.clear();
|
|
self.posts.clear();
|
|
}
|
|
|
|
pub fn last_updated(&self) -> Option<DateTime<Utc>> {
|
|
self.iter().map(|post| post.updated()).max()
|
|
}
|
|
|
|
pub fn iter(&self)
|
|
-> impl '_
|
|
+ Iterator<Item = &Arc<Post>>
|
|
+ ExactSizeIterator
|
|
+ FusedIterator
|
|
+ Clone
|
|
{
|
|
self.posts.values()
|
|
}
|
|
|
|
pub fn iter_by_created(&self)
|
|
-> impl '_
|
|
+ Iterator<Item = &Arc<Post>>
|
|
+ DoubleEndedIterator
|
|
+ ExactSizeIterator
|
|
+ FusedIterator
|
|
+ Clone
|
|
{
|
|
// For each entry of the created index, look up the corresponding post in the posts map and
|
|
// return the post. Every entry in the created index should contain the ID of a post in the
|
|
// posts map, so the `expect` should never fail.
|
|
self.created_ix
|
|
.iter()
|
|
.map(|entry| self.get(&entry.id)
|
|
.expect("invalid entry in `created_ix` pointing to a post that does not exist"))
|
|
}
|
|
}
|
|
|
|
impl Default for PostsStore {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
|
struct CreatedIxEntry {
|
|
created: DateTime<Utc>,
|
|
id: PostId,
|
|
}
|
|
|
|
impl CreatedIxEntry {
|
|
fn new(post: &Post) -> Self {
|
|
Self {
|
|
created: post.created(),
|
|
id: post.id().clone(),
|
|
}
|
|
}
|
|
}
|