chore(web): unified caches under trait

This commit is contained in:
əlemi 2024-07-03 04:05:49 +02:00
parent 64ab2c3bb9
commit 1605557329
Signed by: alemi
GPG key ID: A4895B84D311642C
9 changed files with 76 additions and 51 deletions

1
Cargo.lock generated
View file

@ -4892,6 +4892,7 @@ name = "upub-web"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"apb", "apb",
"async-trait",
"chrono", "chrono",
"console_error_panic_hook", "console_error_panic_hook",
"dashmap", "dashmap",

View file

@ -12,6 +12,8 @@ repository = "https://git.alemi.dev/upub.git"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
async-trait = "0.1"
lazy_static = "1.4"
tracing = "0.1" tracing = "0.1"
tracing-subscriber = "0.3" tracing-subscriber = "0.3"
tracing-subscriber-wasm = "0.1" tracing-subscriber-wasm = "0.1"
@ -29,7 +31,6 @@ apb = { path = "../apb", features = ["unstructured", "activitypub-fe", "activity
uriproxy = { path = "../utils/uriproxy/" } uriproxy = { path = "../utils/uriproxy/" }
mdhtml = { path = "../utils/mdhtml/" } mdhtml = { path = "../utils/mdhtml/" }
futures = "0.3.30" futures = "0.3.30"
lazy_static = "1.4"
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
jrd = "0.1" jrd = "0.1"
tld = "2.35" tld = "2.35"

View file

@ -19,7 +19,7 @@ pub fn ActorHeader() -> impl IntoView {
.await .await
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
let user = std::sync::Arc::new(user); let user = std::sync::Arc::new(user);
cache::OBJECTS.put(Uri::full(U::Actor, &id), user.clone()); cache::OBJECTS.store(&Uri::full(U::Actor, &id), user.clone());
Ok(user) Ok(user)
}, },
} }

View file

@ -272,7 +272,7 @@ pub fn LikeButton(
new = new.set_likes(apb::Node::object(likes.clone().set_total_items(Some(count + 1)))); new = new.set_likes(apb::Node::object(likes.clone().set_total_items(Some(count + 1))));
} }
} }
cache::OBJECTS.put(target, Arc::new(new)); cache::OBJECTS.store(&target, Arc::new(new));
} }
}, },
Err(e) => tracing::error!("failed sending like: {e}"), Err(e) => tracing::error!("failed sending like: {e}"),

View file

@ -46,6 +46,7 @@ pub struct FiltersConfig {
impl FiltersConfig { impl FiltersConfig {
pub fn visible(&self, item: &crate::Object) -> bool { pub fn visible(&self, item: &crate::Object) -> bool {
use apb::{Object, Activity}; use apb::{Object, Activity};
use crate::Cache;
let type_filter = match item.object_type().unwrap_or(apb::ObjectType::Object) { let type_filter = match item.object_type().unwrap_or(apb::ObjectType::Object) {
apb::ObjectType::Note | apb::ObjectType::Document(_) => self.orphans, apb::ObjectType::Note | apb::ObjectType::Document(_) => self.orphans,

View file

@ -23,38 +23,73 @@ pub const DEFAULT_AVATAR_URL: &str = "https://cdn.alemi.dev/social/gradient.png"
pub const NAME: &str = "μ"; pub const NAME: &str = "μ";
pub const DEFAULT_COLOR: &str = "#BF616A"; pub const DEFAULT_COLOR: &str = "#BF616A";
use std::sync::Arc; use std::{ops::Deref, sync::Arc};
use uriproxy::UriClass; use uriproxy::UriClass;
pub mod cache {
use super::{ObjectCache, WebfingerCache};
lazy_static::lazy_static! {
pub static ref OBJECTS: ObjectCache = ObjectCache::default();
pub static ref WEBFINGER: WebfingerCache = WebfingerCache::default();
}
}
}
pub type Object = Arc<serde_json::Value>; pub type Object = Arc<serde_json::Value>;
#[derive(Debug, Clone, Default)] pub mod cache {
pub struct ObjectCache(Arc<dashmap::DashMap<String, Object>>); use super::DashmapCache;
lazy_static::lazy_static! {
pub static ref OBJECTS: DashmapCache<super::Object> = DashmapCache::default();
pub static ref WEBFINGER: DashmapCache<String> = DashmapCache::default();
}
}
impl ObjectCache { #[derive(Debug)]
pub fn get(&self, k: &str) -> Option<Object> { pub enum LookupStatus<T> {
self.0.get(k).map(|x| x.clone()) Resolving,
Found(T),
NotFound,
}
impl<T> LookupStatus<T> {
fn inner(&self) -> Option<&T> {
if let Self::Found(x) = self {
return Some(x);
}
None
}
}
pub trait Cache {
type Item;
fn lookup(&self, key: &str) -> Option<impl Deref<Target = LookupStatus<Self::Item>>>;
fn store(&self, key: &str, value: Self::Item) -> Option<Self::Item>;
fn get(&self, key: &str) -> Option<Self::Item> where Self::Item : Clone {
Some(self.lookup(key)?.deref().inner()?.clone())
} }
pub fn get_or(&self, k: &str, or: Object) -> Object { fn get_or(&self, key: &str, or: Self::Item) -> Self::Item where Self::Item : Clone {
self.get(k).unwrap_or(or) self.get(key).unwrap_or(or)
} }
pub fn put(&self, k: String, v: Object) { fn get_or_default(&self, key: &str) -> Self::Item where Self::Item : Clone + Default {
self.0.insert(k, v); self.get(key).unwrap_or_default()
}
}
#[derive(Default, Clone)]
pub struct DashmapCache<T>(Arc<dashmap::DashMap<String, LookupStatus<T>>>);
impl<T> Cache for DashmapCache<T> {
type Item = T;
fn lookup(&self, key: &str) -> Option<impl Deref<Target = LookupStatus<Self::Item>>> {
self.0.get(key)
} }
fn store(&self, key: &str, value: Self::Item) -> Option<Self::Item> {
self.0.insert(key.to_string(), LookupStatus::Found(value))
.and_then(|x| if let LookupStatus::Found(x) = x { Some(x) } else { None } )
}
}
// TODO would be cool unifying a bit the fetch code too
impl DashmapCache<Object> {
pub async fn fetch(&self, k: &str, kind: UriClass) -> reqwest::Result<Object> { pub async fn fetch(&self, k: &str, kind: UriClass) -> reqwest::Result<Object> {
match self.get(k) { match self.get(k) {
Some(x) => Ok(x), Some(x) => Ok(x),
@ -63,43 +98,30 @@ impl ObjectCache {
.await? .await?
.json::<serde_json::Value>() .json::<serde_json::Value>()
.await?; .await?;
self.put(k.to_string(), Arc::new(obj)); self.store(k, Arc::new(obj));
Ok(self.get(k).expect("not found in cache after insertion")) Ok(self.get(k).expect("not found in cache after insertion"))
} }
} }
} }
} }
#[derive(Debug, Clone)] impl DashmapCache<String> {
enum LookupStatus {
Resolving,
Found(String),
NotFound,
}
#[derive(Debug, Clone, Default)]
pub struct WebfingerCache(Arc<dashmap::DashMap<String, LookupStatus>>);
impl WebfingerCache {
pub async fn blocking_resolve(&self, user: &str, domain: &str) -> Option<String> { pub async fn blocking_resolve(&self, user: &str, domain: &str) -> Option<String> {
if let Some(x) = self.get(user, domain) { return Some(x); } if let Some(x) = self.resource(user, domain) { return Some(x); }
self.fetch(user, domain).await; self.fetch(user, domain).await;
self.get(user, domain) self.resource(user, domain)
} }
pub fn resolve(&self, user: &str, domain: &str) -> Option<String> { pub fn resolve(&self, user: &str, domain: &str) -> Option<String> {
if let Some(x) = self.get(user, domain) { return Some(x); } if let Some(x) = self.resource(user, domain) { return Some(x); }
let (_self, user, domain) = (self.clone(), user.to_string(), domain.to_string()); let (_self, user, domain) = (self.clone(), user.to_string(), domain.to_string());
leptos::spawn_local(async move { _self.fetch(&user, &domain).await }); leptos::spawn_local(async move { _self.fetch(&user, &domain).await });
None None
} }
fn get(&self, user: &str, domain: &str) -> Option<String> { fn resource(&self, user: &str, domain: &str) -> Option<String> {
let query = format!("{user}@{domain}"); let query = format!("{user}@{domain}");
match self.0.get(&query).map(|x| (*x).clone())? { self.get(&query)
LookupStatus::Resolving | LookupStatus::NotFound => None,
LookupStatus::Found(x) => Some(x),
}
} }
async fn fetch(&self, user: &str, domain: &str) { async fn fetch(&self, user: &str, domain: &str) {

View file

@ -28,10 +28,10 @@ pub fn ObjectView() -> impl IntoView {
if let Ok(user) = Http::fetch::<serde_json::Value>( if let Ok(user) = Http::fetch::<serde_json::Value>(
&Uri::api(U::Actor, author, true), auth &Uri::api(U::Actor, author, true), auth
).await { ).await {
cache::OBJECTS.put(Uri::full(U::Actor, author), Arc::new(user)); cache::OBJECTS.store(&Uri::full(U::Actor, author), Arc::new(user));
} }
} }
cache::OBJECTS.put(Uri::full(U::Object, &oid), obj.clone()); cache::OBJECTS.store(&Uri::full(U::Object, &oid), obj.clone());
obj obj
} }
}; };

View file

@ -1,7 +1,7 @@
pub use crate::{ pub use crate::{
URL_BASE, URL_BASE,
Http, Uri, Http, Uri,
cache, Cache, cache, // TODO move Cache under cache
app::{Feeds, Loader}, app::{Feeds, Loader},
auth::Auth, auth::Auth,
page::*, page::*,

View file

@ -115,7 +115,7 @@ async fn process_activities(activities: Vec<serde_json::Value>, auth: Auth) -> V
actors_seen.insert(attributed_to); actors_seen.insert(attributed_to);
} }
if let Ok(object_uri) = object.id() { if let Ok(object_uri) = object.id() {
cache::OBJECTS.put(object_uri.to_string(), Arc::new(object.clone())); cache::OBJECTS.store(object_uri, Arc::new(object.clone()));
} else { } else {
tracing::warn!("embedded object without id: {object:?}"); tracing::warn!("embedded object without id: {object:?}");
} }
@ -136,8 +136,8 @@ async fn process_activities(activities: Vec<serde_json::Value>, auth: Auth) -> V
let object_id = activity.object().id().str(); let object_id = activity.object().id().str();
if let Some(activity_id) = activity.id().str() { if let Some(activity_id) = activity.id().str() {
out.push(activity_id.to_string()); out.push(activity_id.to_string());
cache::OBJECTS.put( cache::OBJECTS.store(
activity_id.to_string(), &activity_id,
Arc::new(activity.clone().set_object(apb::Node::maybe_link(object_id))) Arc::new(activity.clone().set_object(apb::Node::maybe_link(object_id)))
); );
} else if let Some(object_id) = activity.object().id().str() { } else if let Some(object_id) = activity.object().id().str() {
@ -170,7 +170,7 @@ async fn process_activities(activities: Vec<serde_json::Value>, auth: Auth) -> V
async fn fetch_and_update(kind: U, id: String, auth: Auth) { async fn fetch_and_update(kind: U, id: String, auth: Auth) {
match Http::fetch(&Uri::api(kind, &id, false), auth).await { match Http::fetch(&Uri::api(kind, &id, false), auth).await {
Ok(data) => cache::OBJECTS.put(id, Arc::new(data)), Ok(data) => { cache::OBJECTS.store(&id, Arc::new(data)); },
Err(e) => console_warn(&format!("could not fetch '{id}': {e}")), Err(e) => console_warn(&format!("could not fetch '{id}': {e}")),
} }
} }