update browse panel
This commit is contained in:
+119
-6
@@ -114,6 +114,18 @@ fn is_own_subscription(id: &SubscriptionId) -> bool {
|
||||
|| id.starts_with(PAGE_PREFIX)
|
||||
}
|
||||
|
||||
/// The group id and member count of a group members event.
|
||||
fn parse_members(event: &Event) -> Option<(GroupId, usize)> {
|
||||
if event.kind != Kind::GroupMembers {
|
||||
return None;
|
||||
}
|
||||
|
||||
let id = GroupId::new(event.tags.identifier()?).ok()?;
|
||||
let members = event.tags.public_keys().collect::<HashSet<_>>().len();
|
||||
|
||||
Some((id, members))
|
||||
}
|
||||
|
||||
impl EventEmitter<GroupsEvent> for GroupsRegistry {}
|
||||
|
||||
pub struct GroupsRegistry {
|
||||
@@ -282,13 +294,15 @@ impl GroupsRegistry {
|
||||
self.tasks.push(cx.background_spawn(async move {
|
||||
client.add_relay(relay.clone()).and_connect().await.ok();
|
||||
|
||||
let filter = Filter::new().kind(Kind::GroupMetadata).limit(BROWSE_LIMIT);
|
||||
let metadata = Filter::new().kind(Kind::GroupMetadata).limit(BROWSE_LIMIT);
|
||||
let members = Filter::new().kind(Kind::GroupMembers).limit(BROWSE_LIMIT);
|
||||
|
||||
let opts = SubscribeAutoCloseOptions::default()
|
||||
.exit_policy(ReqExitPolicy::ExitOnEOSE)
|
||||
.timeout(Some(BROWSE_TIMEOUT));
|
||||
|
||||
if let Err(error) = client
|
||||
.subscribe(ReqTarget::single(relay.clone(), [filter]))
|
||||
.subscribe(ReqTarget::single(relay.clone(), [metadata, members]))
|
||||
.with_id(id.clone())
|
||||
.close_on(opts)
|
||||
.await
|
||||
@@ -386,6 +400,7 @@ impl GroupsRegistry {
|
||||
return;
|
||||
}
|
||||
|
||||
// Show the cached metadata before the relays answer.
|
||||
let previews = match this.update(cx, |this, cx| this.load_cached_previews(cx)) {
|
||||
Ok(task) => match task.await {
|
||||
Ok(previews) => previews,
|
||||
@@ -400,8 +415,27 @@ impl GroupsRegistry {
|
||||
}
|
||||
};
|
||||
|
||||
// Cached member lists fill the cards' member counts.
|
||||
let members = match this.update(cx, |this, cx| this.load_cached_members(cx)) {
|
||||
Ok(task) => match task.await {
|
||||
Ok(members) => members,
|
||||
Err(error) => {
|
||||
log::warn!("nip29: loading the cached group members failed: {error}");
|
||||
Vec::new()
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
log::warn!("nip29: discovering the groups failed: {error}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(error) = this.update(cx, |this, cx| {
|
||||
this.absorb_cached_previews(previews, cx);
|
||||
this.absorb_cached_members(members, cx);
|
||||
|
||||
// The contacts' lists resolve groups on relays outside the
|
||||
// pool, the sweep covers every relay in it.
|
||||
this.subscribe_contacts(authors, cx);
|
||||
this.subscribe_discovery(cx);
|
||||
}) {
|
||||
@@ -483,6 +517,59 @@ impl GroupsRegistry {
|
||||
});
|
||||
}
|
||||
|
||||
/// Load the cached member lists for the referenced groups from the database.
|
||||
fn load_cached_members(&self, cx: &mut Context<Self>) -> Task<Result<Vec<Event>, Error>> {
|
||||
let refs = self.discovery.read(cx).refs().clone();
|
||||
|
||||
if refs.is_empty() {
|
||||
return Task::ready(Ok(Vec::new()));
|
||||
}
|
||||
|
||||
let nostr = NostrRegistry::global(cx);
|
||||
let client = nostr.read(cx).client();
|
||||
|
||||
let ids: HashSet<&str> = refs.values().flatten().map(GroupId::as_str).collect();
|
||||
let filter = Filter::new().kind(Kind::GroupMembers).identifiers(ids);
|
||||
|
||||
cx.background_spawn(async move {
|
||||
Ok(client.database().query(filter).await?.into_iter().collect())
|
||||
})
|
||||
}
|
||||
|
||||
/// Seed the member counts from the cached member lists.
|
||||
fn absorb_cached_members(&mut self, members: Vec<Event>, cx: &mut Context<Self>) {
|
||||
let newest: HashMap<GroupId, (Timestamp, usize)> =
|
||||
members
|
||||
.into_iter()
|
||||
.fold(HashMap::new(), |mut newest, event| {
|
||||
let Some((id, count)) = parse_members(&event) else {
|
||||
return newest;
|
||||
};
|
||||
|
||||
match newest.get(&id) {
|
||||
Some((created_at, _)) if *created_at >= event.created_at => newest,
|
||||
_ => {
|
||||
newest.insert(id, (event.created_at, count));
|
||||
newest
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for (relay, ids) in self.discovery.read(cx).refs().clone() {
|
||||
for id in ids {
|
||||
let Some((_, count)) = newest.get(&id) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let count = *count;
|
||||
|
||||
self.discovery.update(cx, |discovery, cx| {
|
||||
discovery.set_members(GroupKey::new(relay.clone(), id.clone()), count, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Subscribe to the contacts' public group lists.
|
||||
fn subscribe_contacts(&mut self, authors: HashSet<PublicKey>, cx: &mut Context<Self>) {
|
||||
if authors.is_empty() {
|
||||
@@ -531,7 +618,8 @@ impl GroupsRegistry {
|
||||
let nostr = NostrRegistry::global(cx);
|
||||
let client = nostr.read(cx).client();
|
||||
|
||||
let filter = Filter::new().kind(Kind::GroupMetadata).limit(BROWSE_LIMIT);
|
||||
let metadata = Filter::new().kind(Kind::GroupMetadata).limit(BROWSE_LIMIT);
|
||||
let members = Filter::new().kind(Kind::GroupMembers).limit(BROWSE_LIMIT);
|
||||
|
||||
let opts = SubscribeAutoCloseOptions::default()
|
||||
.exit_policy(ReqExitPolicy::ExitOnEOSE)
|
||||
@@ -541,7 +629,7 @@ impl GroupsRegistry {
|
||||
|
||||
self.tasks.push(cx.background_spawn(async move {
|
||||
let refused = match client
|
||||
.subscribe(ReqTarget::auto([filter]))
|
||||
.subscribe(ReqTarget::auto([metadata, members]))
|
||||
.with_id(discovery_subscription_id())
|
||||
.close_on(opts)
|
||||
.await
|
||||
@@ -564,6 +652,13 @@ impl GroupsRegistry {
|
||||
|
||||
/// Absorb a discovered group metadata event from the relay serving it.
|
||||
fn absorb_discovered(&mut self, relay: RelayUrl, event: &Event, cx: &mut Context<Self>) {
|
||||
if let Some((id, members)) = parse_members(event) {
|
||||
self.discovery.update(cx, |discovery, cx| {
|
||||
discovery.set_members(GroupKey::new(relay, id), members, cx);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let metadata = match GroupMetadata::parse(event) {
|
||||
Ok(metadata) => metadata,
|
||||
Err(error) => {
|
||||
@@ -609,10 +704,14 @@ impl GroupsRegistry {
|
||||
});
|
||||
let client = client.clone();
|
||||
|
||||
let filter = Filter::new()
|
||||
let metadata = Filter::new()
|
||||
.kind(Kind::GroupMetadata)
|
||||
.identifiers(ids.iter().map(GroupId::as_str));
|
||||
|
||||
let members = Filter::new()
|
||||
.kind(Kind::GroupMembers)
|
||||
.identifiers(ids.iter().map(GroupId::as_str));
|
||||
|
||||
let opts = SubscribeAutoCloseOptions::default()
|
||||
.exit_policy(ReqExitPolicy::ExitOnEOSE)
|
||||
.timeout(Some(BROWSE_TIMEOUT));
|
||||
@@ -623,7 +722,7 @@ impl GroupsRegistry {
|
||||
client.add_relay(relay.clone()).and_connect().await.ok();
|
||||
|
||||
let refused = match client
|
||||
.subscribe(ReqTarget::single(relay.clone(), [filter]))
|
||||
.subscribe(ReqTarget::single(relay.clone(), [metadata, members]))
|
||||
.with_id(id.clone())
|
||||
.close_on(opts)
|
||||
.await
|
||||
@@ -677,6 +776,13 @@ impl GroupsRegistry {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some((group, members)) = parse_members(event) {
|
||||
self.discovery.update(cx, |discovery, cx| {
|
||||
discovery.set_members(GroupKey::new(relay, group), members, cx);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let metadata = match GroupMetadata::parse(event) {
|
||||
Ok(metadata) => metadata,
|
||||
Err(error) => {
|
||||
@@ -1316,6 +1422,13 @@ impl GroupsRegistry {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some((group, members)) = parse_members(event) {
|
||||
self.discovery.update(cx, |discovery, cx| {
|
||||
discovery.set_members(GroupKey::new(relay, group), members, cx);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let Ok(metadata) = GroupMetadata::parse(event) else {
|
||||
log::warn!("nip29: ignoring malformed group metadata from {relay}");
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user