forked from alemi/upub
chore(web): unified caches under trait
This commit is contained in:
parent
64ab2c3bb9
commit
1605557329
9 changed files with 76 additions and 51 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -4892,6 +4892,7 @@ name = "upub-web"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"apb",
|
||||
"async-trait",
|
||||
"chrono",
|
||||
"console_error_panic_hook",
|
||||
"dashmap",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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}"),
|
||||
|
|
|
@ -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,
|
||||
|
|
106
web/src/lib.rs
106
web/src/lib.rs
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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::*,
|
||||
|
|
|
@ -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}")),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue