This commit is contained in:
2026-10-20 14:36:56 +07:00
parent 0f0216051f
commit d64da17c5c
6 changed files with 70 additions and 314 deletions
+14 -1
View File
@@ -19,7 +19,6 @@ pub use wot::*;
pub fn init(cx: &mut App) {
PersonRegistry::set_global(cx.new(PersonRegistry::new), cx);
WebOfTrust::set_global(cx.new(WebOfTrust::new), cx);
}
struct GlobalPersonRegistry(Entity<PersonRegistry>);
@@ -63,6 +62,8 @@ enum Dispatch {
pub struct PersonRegistry {
/// Public keys with a metadata request in flight
in_flight: RwLock<HashSet<PublicKey>>,
/// Web of trust graph of the signed-in user
wot: Entity<WebOfTrust>,
sender: flume::Sender<PublicKey>,
tasks: SmallVec<[Task<()>; 4]>,
_subscriptions: SmallVec<[Subscription; 1]>,
@@ -83,6 +84,7 @@ impl PersonRegistry {
fn new(cx: &mut Context<Self>) -> Self {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let wot = cx.new(WebOfTrust::new);
// Channel for communication between nostr and gpui
let (tx, rx) = flume::bounded::<Dispatch>(100);
@@ -125,17 +127,28 @@ impl PersonRegistry {
let subscription = cx.subscribe(&nostr, |this, _nostr, event, cx| {
if event.signer_changed() {
this.get_emojis(cx);
this.wot.update(cx, |wot, cx| {
wot.reset(cx);
wot.hydrate(cx);
});
}
});
Self {
in_flight: RwLock::new(HashSet::new()),
wot,
sender: metadata_tx,
tasks,
_subscriptions: smallvec![subscription],
}
}
/// The web of trust graph of the signed-in user
pub fn wot(&self) -> Entity<WebOfTrust> {
self.wot.clone()
}
/// Handle nostr notifications
async fn handle_notifications(client: &Client, tx: &flume::Sender<Dispatch>) {
let mut notifications = client.notifications();
+14 -65
View File
@@ -1,6 +1,6 @@
use std::collections::{HashMap, HashSet};
use gpui::{App, AppContext, Context, Entity, EventEmitter, Global, Subscription, Task};
use gpui::{AppContext, Context, EventEmitter, Task};
use instant::Duration;
use nostr_sdk::prelude::*;
use smallvec::{SmallVec, smallvec};
@@ -11,20 +11,11 @@ const MAX_FOLLOWS: usize = 2000;
/// Cap a followed author's contact list to bound hostile publishers
const MAX_TAGS_PER_LIST: usize = 5000;
/// Time between reconciliations with the relays
const SYNC_INTERVAL: u64 = 15 * 60;
const INTERVAL: u64 = 15 * 60;
struct GlobalWebOfTrust(Entity<WebOfTrust>);
impl Global for GlobalWebOfTrust {}
/// A change to the web of trust
/// The web of trust graph changed
#[derive(Debug, Clone)]
pub enum WoTEvent {
/// The graph changed
Updated,
/// The initial load finished
Ready,
}
pub struct WoTEvent;
impl EventEmitter<WoTEvent> for WebOfTrust {}
@@ -48,38 +39,22 @@ pub struct WebOfTrust {
disabled: bool,
/// Whether the initial load has finished
ready: bool,
/// Whether the next refresh completes the initial load
initial: bool,
/// Task loading the graph, replaced by newer loads
hydration: Option<Task<()>>,
/// Tasks for asynchronous operations
tasks: SmallVec<[Task<()>; 4]>,
/// Event subscriptions
_subscriptions: SmallVec<[Subscription; 1]>,
}
impl WebOfTrust {
/// Retrieve the global web of trust
pub fn global(cx: &App) -> Entity<Self> {
cx.global::<GlobalWebOfTrust>().0.clone()
}
/// Set the global web of trust instance
pub(crate) fn set_global(state: Entity<Self>, cx: &mut App) {
cx.set_global(GlobalWebOfTrust(state));
}
/// Create a new web of trust instance
pub(crate) fn new(cx: &mut Context<Self>) -> Self {
let nostr = NostrRegistry::global(cx);
let entity = cx.entity().downgrade();
let mut tasks = smallvec![];
tasks.push(cx.spawn(async move |this, cx| {
loop {
cx.background_executor()
.timer(Duration::from_secs(SYNC_INTERVAL))
.timer(Duration::from_secs(INTERVAL))
.await;
// Ignore errors, the next refresh will retry
let _ = this.update(cx, |this, cx| {
@@ -88,13 +63,6 @@ impl WebOfTrust {
}
}));
let subscription = cx.subscribe(&nostr, |this, _nostr, event, cx| {
if event.signer_changed() {
this.reset(cx);
this.hydrate(cx);
}
});
cx.defer(move |cx| {
let _ = entity.update(cx, |this, cx| {
this.hydrate(cx);
@@ -109,23 +77,21 @@ impl WebOfTrust {
reverse: HashMap::new(),
disabled: false,
ready: false,
initial: false,
hydration: None,
tasks,
_subscriptions: smallvec![subscription],
}
}
/// Load the graph for the current account
fn hydrate(&mut self, cx: &mut Context<Self>) {
pub(crate) fn hydrate(&mut self, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let Some(self_key) = nostr.read(cx).current_user() else {
return;
};
self.self_key = Some(self_key);
self.initial = true;
// The locally stored contact list, applied before the relays respond
let local: Task<Option<Event>> = cx.background_spawn(async move {
@@ -155,7 +121,7 @@ impl WebOfTrust {
// Credit the local graph before the relays respond
this.update(cx, |this, cx| {
if this.self_key == Some(self_key) && this.store_follows(at, snapshot.clone()) {
cx.emit(WoTEvent::Updated);
cx.emit(WoTEvent);
cx.notify();
}
})
@@ -206,16 +172,12 @@ impl WebOfTrust {
let mut grew = false;
this.update(cx, |this, cx| {
let was_ready = this.ready;
let changed = this.apply_latest(self_key, events, cx);
this.ready = true;
if this.initial {
this.initial = false;
this.ready = true;
cx.emit(WoTEvent::Ready);
}
if changed {
cx.emit(WoTEvent::Updated);
if changed || !was_ready {
cx.emit(WoTEvent);
cx.notify();
}
@@ -407,7 +369,7 @@ impl WebOfTrust {
}
/// Clear the graph on sign out or account switch
fn reset(&mut self, cx: &mut Context<Self>) {
pub(crate) fn reset(&mut self, cx: &mut Context<Self>) {
self.self_key = None;
self.follows.clear();
self.follows_at = None;
@@ -415,10 +377,9 @@ impl WebOfTrust {
self.reverse.clear();
self.disabled = false;
self.ready = false;
self.initial = false;
self.hydration = None;
cx.emit(WoTEvent::Updated);
cx.emit(WoTEvent);
cx.notify();
}
@@ -450,20 +411,10 @@ impl WebOfTrust {
})
}
/// Whether the initial load has finished
pub fn is_ready(&self) -> bool {
self.ready
}
/// Whether the graph is loaded and score queries are meaningful
pub fn is_loaded(&self) -> bool {
self.ready && !self.disabled
}
/// Whether scoring is disabled due to an oversized follow set
pub fn is_disabled(&self) -> bool {
self.disabled
}
}
#[cfg(test)]
@@ -487,10 +438,8 @@ mod tests {
reverse: HashMap::new(),
disabled: false,
ready: false,
initial: false,
hydration: None,
tasks: smallvec![],
_subscriptions: smallvec![],
}
}