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"
dependencies = [
"apb",
"async-trait",
"chrono",
"console_error_panic_hook",
"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
[dependencies]
async-trait = "0.1"
lazy_static = "1.4"
tracing = "0.1"
tracing-subscriber = "0.3"
tracing-subscriber-wasm = "0.1"
@ -29,7 +31,6 @@ apb = { path = "../apb", features = ["unstructured", "activitypub-fe", "activity
uriproxy = { path = "../utils/uriproxy/" }
mdhtml = { path = "../utils/mdhtml/" }
futures = "0.3.30"
lazy_static = "1.4"
chrono = { version = "0.4", features = ["serde"] }
jrd = "0.1"
tld = "2.35"

View file

@ -19,7 +19,7 @@ pub fn ActorHeader() -> impl IntoView {
.await
.map_err(|e| e.to_string())?;
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)
},
}

View file

@ -272,7 +272,7 @@ pub fn LikeButton(
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}"),

View file

@ -46,6 +46,7 @@ pub struct FiltersConfig {
impl FiltersConfig {
pub fn visible(&self, item: &crate::Object) -> bool {
use apb::{Object, Activity};
use crate::Cache;
let type_filter = match item.object_type().unwrap_or(apb::ObjectType::Object) {
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 DEFAULT_COLOR: &str = "#BF616A";
use std::sync::Arc;
use std::{ops::Deref, sync::Arc};
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>;
#[derive(Debug, Clone, Default)]
pub struct ObjectCache(Arc<dashmap::DashMap<String, Object>>);
impl ObjectCache {
pub fn get(&self, k: &str) -> Option<Object> {
self.0.get(k).map(|x| x.clone())
pub mod cache {
use super::DashmapCache;
lazy_static::lazy_static! {
pub static ref OBJECTS: DashmapCache<super::Object> = DashmapCache::default();
pub static ref WEBFINGER: DashmapCache<String> = DashmapCache::default();
}
}
pub fn get_or(&self, k: &str, or: Object) -> Object {
self.get(k).unwrap_or(or)
#[derive(Debug)]
pub enum LookupStatus<T> {
Resolving,
Found(T),
NotFound,
}
pub fn put(&self, k: String, v: Object) {
self.0.insert(k, v);
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())
}
fn get_or(&self, key: &str, or: Self::Item) -> Self::Item where Self::Item : Clone {
self.get(key).unwrap_or(or)
}
fn get_or_default(&self, key: &str) -> Self::Item where Self::Item : Clone + Default {
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> {
match self.get(k) {
Some(x) => Ok(x),
@ -63,43 +98,30 @@ impl ObjectCache {
.await?
.json::<serde_json::Value>()
.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"))
}
}
}
}
#[derive(Debug, Clone)]
enum LookupStatus {
Resolving,
Found(String),
NotFound,
}
#[derive(Debug, Clone, Default)]
pub struct WebfingerCache(Arc<dashmap::DashMap<String, LookupStatus>>);
impl WebfingerCache {
impl DashmapCache<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.get(user, domain)
self.resource(user, domain)
}
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());
leptos::spawn_local(async move { _self.fetch(&user, &domain).await });
None
}
fn get(&self, user: &str, domain: &str) -> Option<String> {
fn resource(&self, user: &str, domain: &str) -> Option<String> {
let query = format!("{user}@{domain}");
match self.0.get(&query).map(|x| (*x).clone())? {
LookupStatus::Resolving | LookupStatus::NotFound => None,
LookupStatus::Found(x) => Some(x),
}
self.get(&query)
}
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>(
&Uri::api(U::Actor, author, true), auth
).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
}
};

View file

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

View file

@ -115,7 +115,7 @@ async fn process_activities(activities: Vec<serde_json::Value>, auth: Auth) -> V
actors_seen.insert(attributed_to);
}
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 {
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();
if let Some(activity_id) = activity.id().str() {
out.push(activity_id.to_string());
cache::OBJECTS.put(
activity_id.to_string(),
cache::OBJECTS.store(
&activity_id,
Arc::new(activity.clone().set_object(apb::Node::maybe_link(object_id)))
);
} 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) {
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}")),
}
}