This commit is contained in:
2026-07-27 15:02:50 +07:00
parent b349656c56
commit 2ff83079b8
9 changed files with 167 additions and 181 deletions

1
Cargo.lock generated
View File

@@ -1056,6 +1056,7 @@ dependencies = [
"common", "common",
"device", "device",
"flume 0.11.1", "flume 0.11.1",
"futures",
"fuzzy-matcher", "fuzzy-matcher",
"gpui", "gpui",
"itertools 0.13.0", "itertools 0.13.0",

View File

@@ -20,6 +20,7 @@ smallvec.workspace = true
log.workspace = true log.workspace = true
flume.workspace = true flume.workspace = true
futures.workspace = true
fuzzy-matcher = "0.3.7" fuzzy-matcher = "0.3.7"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]

View File

@@ -82,15 +82,15 @@ pub struct ChatRegistry {
rooms: Vec<Entity<Room>>, rooms: Vec<Entity<Room>>,
/// Events that failed to unwrap for any reason /// Events that failed to unwrap for any reason
trashes: Entity<BTreeSet<FailedMessage>>, trash: Entity<BTreeSet<FailedMessage>>,
/// Tracking events seen on which relays in the current session /// Tracking events seen on which relays in the current session
seens: Arc<RwLock<HashMap<EventId, HashSet<RelayUrl>>>>, seen: Arc<RwLock<HashMap<EventId, HashSet<RelayUrl>>>>,
/// Mapping of unwrapped event ids to their gift wrap event ids /// Mapping of unwrapped event ids to their gift wrap event ids
event_map: Arc<RwLock<HashMap<EventId, EventId>>>, event_map: Arc<RwLock<HashMap<EventId, EventId>>>,
/// Tracking the status of unwrapping gift wrap events. /// True while the initial event backlog is still loading
tracking: Arc<AtomicBool>, tracking: Arc<AtomicBool>,
/// Channel for sending signals to the UI. /// Channel for sending signals to the UI.
@@ -108,10 +108,31 @@ pub struct ChatRegistry {
/// Signal consumer task (cancelled on signer change) /// Signal consumer task (cancelled on signer change)
signal_consumer: Option<Task<Result<(), Error>>>, signal_consumer: Option<Task<Result<(), Error>>>,
/// Fuzzy matcher for room search (cached; intentionally excluded from Debug)
#[allow(dead_code)]
matcher: CachedMatcher,
/// Subscriptions /// Subscriptions
_subscriptions: SmallVec<[Subscription; 2]>, _subscriptions: SmallVec<[Subscription; 2]>,
} }
/// Wrapper to provide Debug for SkimMatcherV2
struct CachedMatcher(SkimMatcherV2);
impl std::fmt::Debug for CachedMatcher {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("CachedMatcher { .. }")
}
}
impl std::ops::Deref for CachedMatcher {
type Target = SkimMatcherV2;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl EventEmitter<ChatEvent> for ChatRegistry {} impl EventEmitter<ChatEvent> for ChatRegistry {}
impl ChatRegistry { impl ChatRegistry {
@@ -145,16 +166,16 @@ impl ChatRegistry {
// Run at the end of the current cycle // Run at the end of the current cycle
cx.defer_in(window, |this, _window, cx| { cx.defer_in(window, |this, _window, cx| {
this.tracking(cx);
this.get_rooms(cx); this.get_rooms(cx);
}); });
Self { Self {
rooms: vec![], rooms: vec![],
trashes: cx.new(|_| BTreeSet::default()), trash: cx.new(|_| BTreeSet::default()),
seens: Arc::new(RwLock::new(HashMap::default())), seen: Arc::new(RwLock::new(HashMap::default())),
event_map: Arc::new(RwLock::new(HashMap::default())), event_map: Arc::new(RwLock::new(HashMap::default())),
tracking: Arc::new(AtomicBool::new(false)), tracking: Arc::new(AtomicBool::new(true)),
matcher: CachedMatcher(SkimMatcherV2::default()),
signal_rx: rx, signal_rx: rx,
signal_tx: tx, signal_tx: tx,
tasks: smallvec![], tasks: smallvec![],
@@ -174,12 +195,10 @@ impl ChatRegistry {
let client = nostr.read(cx).client(); let client = nostr.read(cx).client();
let signer = nostr.read(cx).signer(); let signer = nostr.read(cx).signer();
let tracking = self.tracking.clone(); let seen = self.seen.clone();
let seens = self.seens.clone();
let event_map = self.event_map.clone(); let event_map = self.event_map.clone();
let trashes = self.trashes.downgrade(); let trash = self.trash.downgrade();
let initialized_at = Timestamp::now();
let sub_id1 = SubscriptionId::new(DEVICE_GIFTWRAP); let sub_id1 = SubscriptionId::new(DEVICE_GIFTWRAP);
let sub_id2 = SubscriptionId::new(USER_GIFTWRAP); let sub_id2 = SubscriptionId::new(USER_GIFTWRAP);
@@ -190,16 +209,19 @@ impl ChatRegistry {
self.notification_listener = Some(cx.background_spawn(async move { self.notification_listener = Some(cx.background_spawn(async move {
let mut notifications = client.notifications(); let mut notifications = client.notifications();
let mut processed_events = HashSet::new(); let mut processed_events = HashSet::new();
const MAX_PROCESSED: usize = 10_000;
while let Some(notification) = notifications.next().await { while let Some(notification) = notifications.next().await {
let ClientNotification::Message { message, relay_url } = notification else { let ClientNotification::Message { message, relay_url } = notification else {
// Skip non-message notifications
continue; continue;
}; };
match *message { match *message {
RelayMessage::Event { event, .. } => { RelayMessage::Event { event, .. } => {
// De-duplicate events by their ID // Prune the dedup set before it grows unbounded
if processed_events.len() >= MAX_PROCESSED {
processed_events.clear();
}
if !processed_events.insert(event.id) { if !processed_events.insert(event.id) {
continue; continue;
} }
@@ -207,7 +229,6 @@ impl ChatRegistry {
// Handle msg relays event to determine when the app is ready to subscribe // Handle msg relays event to determine when the app is ready to subscribe
if event.kind == Kind::InboxRelays { if event.kind == Kind::InboxRelays {
let current_user = signer.get_public_key_async().await?; let current_user = signer.get_public_key_async().await?;
// Emit the inbox ready signal
if event.pubkey == current_user { if event.pubkey == current_user {
tx.send_async(Signal::InboxReady).await?; tx.send_async(Signal::InboxReady).await?;
} }
@@ -220,14 +241,13 @@ impl ChatRegistry {
// Keep track of which relays have seen this event // Keep track of which relays have seen this event
{ {
let mut seens = seens.write().unwrap(); let mut seen = seen.write().unwrap();
seens.entry(event.id).or_default().insert(relay_url); seen.entry(event.id).or_default().insert(relay_url);
} }
// Extract the rumor from the gift wrap event // Extract the rumor from the gift wrap event
match extract_rumor(&client, &signer, event.as_ref()).await { match extract_rumor(&client, &signer, event.as_ref()).await {
Ok(rumor) => { Ok(rumor) => {
// Map the rumor id to the gift wrap event id for later lookup
let Some(rumor_id) = rumor.id else { let Some(rumor_id) = rumor.id else {
log::error!("Rumor missing id after ensure_id"); log::error!("Rumor missing id after ensure_id");
continue; continue;
@@ -237,20 +257,14 @@ impl ChatRegistry {
event_map.insert(rumor_id, event.id); event_map.insert(rumor_id, event.id);
} }
// Check if the rumor has a recipient
if rumor.tags.is_empty() { if rumor.tags.is_empty() {
let signal = Signal::error(&event, "Recipient is missing"); let signal = Signal::error(&event, "Recipient is missing");
tx.send_async(signal).await?; tx.send_async(signal).await?;
} }
// Check if the rumor was created after the chat was initialized (for detecting new messages) // Emit message for both new and backlog events
if rumor.created_at >= initialized_at { let signal = Signal::message(event.id, rumor);
let signal = Signal::message(event.id, rumor); tx.send_async(signal).await?;
tx.send_async(signal).await?;
} else {
// Mark the chat still processing new messages
tracking.store(true, Ordering::Release);
}
} }
Err(e) => { Err(e) => {
let reason = format!("Failed to extract rumor: {e}"); let reason = format!("Failed to extract rumor: {e}");
@@ -286,12 +300,13 @@ impl ChatRegistry {
} }
Signal::Eose => { Signal::Eose => {
this.update(cx, |this, cx| { this.update(cx, |this, cx| {
this.get_rooms(cx); this.tracking.store(false, Ordering::Release);
this.sort(cx);
})?; })?;
} }
Signal::Error(trash) => { Signal::Error(failed) => {
trashes.update(cx, |this, cx| { trash.update(cx, |this, cx| {
this.insert(trash); this.insert(failed);
cx.notify(); cx.notify();
})?; })?;
} }
@@ -302,27 +317,6 @@ impl ChatRegistry {
})); }));
} }
/// Check periodically whether old gift-wrap events have finished processing,
/// and refresh rooms once the backlog is caught up.
fn tracking(&mut self, cx: &mut Context<Self>) {
let status = self.tracking.clone();
let tx = self.signal_tx.clone();
let check_interval = Duration::from_secs(15);
self.tasks.push(cx.spawn(async move |_, cx| {
loop {
cx.background_executor().timer(check_interval).await;
// Only trigger a room refresh if old events were being tracked
// (i.e. the notification handler set the flag while catching up).
// `swap` atomically reads and clears the flag.
if status.swap(false, Ordering::AcqRel) {
_ = tx.send_async(Signal::Eose).await;
}
}
}));
}
/// Get all necessary metadata from relays for current user /// Get all necessary metadata from relays for current user
pub fn get_metadata(&mut self, cx: &mut Context<Self>) { pub fn get_metadata(&mut self, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx); let nostr = NostrRegistry::global(cx);
@@ -332,37 +326,29 @@ impl ChatRegistry {
return; return;
}; };
let subscribe = cx.background_spawn({
let client = client.clone();
async move {
let opts =
SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
let msg_relays = Filter::new()
.kind(Kind::InboxRelays)
.author(public_key)
.limit(1);
let contact_list = Filter::new()
.kind(Kind::ContactList)
.author(public_key)
.limit(1);
client
.subscribe(vec![msg_relays, contact_list])
.close_on(opts)
.await
}
});
self.tasks.push(cx.spawn(async move |this, cx| { self.tasks.push(cx.spawn(async move |this, cx| {
// Wait for subscription to complete (or fail silently) // Subscribe to metadata from relays
_ = subscribe.await; let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
let msg_relays = Filter::new()
.kind(Kind::InboxRelays)
.author(public_key)
.limit(1);
let contact_list = Filter::new()
.kind(Kind::ContactList)
.author(public_key)
.limit(1);
_ = client
.subscribe(vec![msg_relays, contact_list])
.close_on(opts)
.await;
// Give relays time to respond // Give relays time to respond
cx.background_executor().timer(Duration::from_secs(5)).await; cx.background_executor().timer(Duration::from_secs(5)).await;
// Verify inbox relays were received
let filter = Filter::new() let filter = Filter::new()
.kind(Kind::InboxRelays) .kind(Kind::InboxRelays)
.author(public_key) .author(public_key)
@@ -436,8 +422,8 @@ impl ChatRegistry {
})); }));
} }
/// Refresh the chat registry, fetching messages and contact list from relays. /// Reload the chat registry, fetching messages and contact list from relays.
pub fn refresh(&mut self, cx: &mut Context<Self>) { pub fn reload(&mut self, cx: &mut Context<Self>) {
self.reset(cx); self.reset(cx);
self.get_metadata(cx); self.get_metadata(cx);
self.get_rooms(cx); self.get_rooms(cx);
@@ -475,22 +461,22 @@ impl ChatRegistry {
/// Count the number of messages seen by a given relay. /// Count the number of messages seen by a given relay.
pub fn count_messages(&self, relay_url: &RelayUrl) -> usize { pub fn count_messages(&self, relay_url: &RelayUrl) -> usize {
self.seens self.seen
.read() .read()
.unwrap() .unwrap()
.values() .values()
.filter(|seen| seen.contains(relay_url)) .filter(|s| s.contains(relay_url))
.count() .count()
} }
/// Count the number of trash messages. /// Count the number of trash messages.
pub fn count_trash_messages(&self, cx: &App) -> usize { pub fn count_trash_messages(&self, cx: &App) -> usize {
self.trashes.read(cx).len() self.trash.read(cx).len()
} }
/// Get the trash messages entity. /// Get the trash messages entity.
pub fn trashes(&self) -> Entity<BTreeSet<FailedMessage>> { pub fn trash(&self) -> Entity<BTreeSet<FailedMessage>> {
self.trashes.clone() self.trash.clone()
} }
/// Get the relays that have seen a given rumor id. /// Get the relays that have seen a given rumor id.
@@ -504,7 +490,7 @@ impl ChatRegistry {
/// Get the relays that have seen a given gift wrap id. /// Get the relays that have seen a given gift wrap id.
pub fn seen_on(&self, id: &EventId) -> HashSet<RelayUrl> { pub fn seen_on(&self, id: &EventId) -> HashSet<RelayUrl> {
self.seens self.seen
.read() .read()
.unwrap() .unwrap()
.get(id) .get(id)
@@ -560,8 +546,6 @@ impl ChatRegistry {
/// Finding rooms based on a query. /// Finding rooms based on a query.
pub fn find(&self, query: &str, cx: &App) -> Vec<Entity<Room>> { pub fn find(&self, query: &str, cx: &App) -> Vec<Entity<Room>> {
let matcher = SkimMatcherV2::default();
if let Ok(public_key) = PublicKey::parse(query) { if let Ok(public_key) = PublicKey::parse(query) {
self.rooms self.rooms
.iter() .iter()
@@ -572,7 +556,7 @@ impl ChatRegistry {
self.rooms self.rooms
.iter() .iter()
.filter(|room| { .filter(|room| {
matcher self.matcher
.fuzzy_match(room.read(cx).display_name(cx).as_ref(), query) .fuzzy_match(room.read(cx).display_name(cx).as_ref(), query)
.is_some() .is_some()
}) })
@@ -584,7 +568,7 @@ impl ChatRegistry {
/// Reset the registry. /// Reset the registry.
pub fn reset(&mut self, cx: &mut Context<Self>) { pub fn reset(&mut self, cx: &mut Context<Self>) {
self.rooms.clear(); self.rooms.clear();
self.trashes.update(cx, |this, cx| { self.trash.update(cx, |this, cx| {
this.clear(); this.clear();
cx.notify(); cx.notify();
}); });

View File

@@ -363,22 +363,31 @@ impl Room {
.exit_policy(ReqExitPolicy::ExitOnEOSE) .exit_policy(ReqExitPolicy::ExitOnEOSE)
.timeout(Some(Duration::from_secs(TIMEOUT))); .timeout(Some(Duration::from_secs(TIMEOUT)));
for public_key in members.into_iter() { let tasks: Vec<_> = members
let inbox = Filter::new() .into_iter()
.author(public_key) .map(|public_key| {
.kind(Kind::InboxRelays) let client = client.clone();
.limit(1); async move {
let inbox = Filter::new()
.author(public_key)
.kind(Kind::InboxRelays)
.limit(1);
let announcement = Filter::new() let announcement = Filter::new()
.author(public_key) .author(public_key)
.kind(Kind::Custom(10044)) .kind(Kind::Custom(10044))
.limit(1); .limit(1);
// Subscribe to the target client
client .subscribe(vec![inbox, announcement])
.subscribe(vec![inbox, announcement]) .close_on(opts)
.close_on(opts) .await
.await?; }
})
.collect();
for result in futures::future::join_all(tasks).await {
result?;
} }
Ok(()) Ok(())
@@ -429,14 +438,6 @@ impl Room {
// Get current user's public key // Get current user's public key
let sender = nostr.read(cx).current_user()?; let sender = nostr.read(cx).current_user()?;
// Get all members, excluding the sender
let members: Vec<Person> = self
.members
.iter()
.filter(|public_key| public_key != &&sender)
.map(|member| persons.read(cx).get(member, cx))
.collect();
// Construct event's tags // Construct event's tags
let mut tags = vec![]; let mut tags = vec![];
@@ -450,8 +451,9 @@ impl Room {
tags.push(Tag::event(id)) tags.push(Tag::event(id))
} }
// Add all receiver tags // Add all receiver tags (no intermediate allocation)
for member in members.into_iter() { for public_key in self.members.iter().filter(|pk| *pk != &sender) {
let member = persons.read(cx).get(public_key, cx);
tags.push( tags.push(
Nip01Tag::PublicKey { Nip01Tag::PublicKey {
public_key: member.public_key(), public_key: member.public_key(),
@@ -473,6 +475,30 @@ impl Room {
Some(event) Some(event)
} }
/// Select the appropriate signer based on signer kind and available keys.
fn select_signer(
signer_kind: &SignerKind,
has_announcement: bool,
encryption_signer: &Option<UniversalSigner>,
user_signer: &UniversalSigner,
) -> UniversalSigner {
match signer_kind {
SignerKind::Auto => {
if has_announcement {
encryption_signer
.clone()
.unwrap_or_else(|| user_signer.clone())
} else {
user_signer.clone()
}
}
SignerKind::Encryption => encryption_signer
.clone()
.expect("encryption signer must be set"),
SignerKind::User => user_signer.clone(),
}
}
/// Send rumor event to all members's messaging relays /// Send rumor event to all members's messaging relays
pub fn send(&self, rumor: UnsignedEvent, cx: &App) -> Option<Task<Vec<SendReport>>> { pub fn send(&self, rumor: UnsignedEvent, cx: &App) -> Option<Task<Vec<SendReport>>> {
let config = self.config.clone(); let config = self.config.clone();
@@ -525,23 +551,12 @@ impl Room {
} }
// Determine the signer to use // Determine the signer to use
let signer = match signer_kind { let signer = Self::select_signer(
SignerKind::Auto => { signer_kind,
if announcement.is_some() announcement.is_some(),
&& let Some(encryption_signer) = encryption_signer.clone() &encryption_signer,
{ &user_signer,
// Safe to unwrap due to earlier checks );
encryption_signer
} else {
user_signer.clone()
}
}
SignerKind::Encryption => {
// Safe to unwrap due to earlier checks
encryption_signer.as_ref().unwrap().clone()
}
SignerKind::User => user_signer.clone(),
};
// Send the gift wrap event and collect the report // Send the gift wrap event and collect the report
match send_gift_wrap(&client, &signer, &member, &rumor, signer_kind).await { match send_gift_wrap(&client, &signer, &member, &rumor, signer_kind).await {
@@ -561,23 +576,12 @@ impl Room {
let public_key = sender.public_key(); let public_key = sender.public_key();
// Determine the signer to use // Determine the signer to use
let signer = match signer_kind { let signer = Self::select_signer(
SignerKind::Auto => { signer_kind,
if sender.announcement().is_some() sender.announcement().is_some(),
&& let Some(encryption_signer) = encryption_signer.clone() &encryption_signer,
{ &user_signer,
// Safe to unwrap due to earlier checks );
encryption_signer
} else {
user_signer.clone()
}
}
SignerKind::Encryption => {
// Safe to unwrap due to earlier checks
encryption_signer.as_ref().unwrap().clone()
}
SignerKind::User => user_signer.clone(),
};
match send_gift_wrap(&client, &signer, &sender, &rumor, signer_kind).await { match send_gift_wrap(&client, &signer, &sender, &rumor, signer_kind).await {
Ok(report) => reports.push(report), Ok(report) => reports.push(report),

View File

@@ -351,13 +351,20 @@ impl ChatPanel {
let content = value.to_string(); let content = value.to_string();
let sent_ids = self.sent_ids.clone(); let sent_ids = self.sent_ids.clone();
// Upgrade room and create the rumor synchronously // Upgrade room and create rumor + send task in a single read lock
let Some(room_entity) = room.upgrade() else { let Some(room_entity) = room.upgrade() else {
return; return;
}; };
let Some(rumor) = room_entity.read(cx).rumor(content.clone(), replies, cx) else { let (rumor, send_task) = match room_entity.read_with(cx, |room, cx| {
window.push_notification("Failed to create message", cx); let rumor = room.rumor(content.clone(), replies, cx)?;
return; let send_task = room.send(rumor.clone(), cx)?;
Some((rumor, send_task))
}) {
Some(pair) => pair,
None => {
window.push_notification("Failed to create message", cx);
return;
}
}; };
let id = rumor.id.expect("rumor must have an id"); let id = rumor.id.expect("rumor must have an id");
@@ -367,12 +374,6 @@ impl ChatPanel {
self.insert_reports(id, vec![], cx); self.insert_reports(id, vec![], cx);
self.clear(window, cx); self.clear(window, cx);
// Get the send task
let Some(send_task) = room_entity.read(cx).send(rumor, cx) else {
window.push_notification("Failed to send message", cx);
return;
};
// Spawn a single task to await the send and update reports // Spawn a single task to await the send and update reports
self.tasks.push(cx.spawn_in(window, async move |this, cx| { self.tasks.push(cx.spawn_in(window, async move |this, cx| {
let outputs = send_task.await; let outputs = send_task.await;

View File

@@ -92,7 +92,6 @@ impl DeviceRegistry {
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self { fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let nostr = NostrRegistry::global(cx); let nostr = NostrRegistry::global(cx);
let settings = AppSettings::global(cx); let settings = AppSettings::global(cx);
let nip4e_enabled = settings.read(cx).is_nip4e_enabled(cx);
let signer = cx.new(|_| None); let signer = cx.new(|_| None);
let mut subscriptions = smallvec![]; let mut subscriptions = smallvec![];
@@ -109,7 +108,7 @@ impl DeviceRegistry {
subscriptions.push( subscriptions.push(
// Observe the user signer // Observe the user signer
cx.subscribe(&nostr, move |this, _nostr, event, cx| { cx.subscribe(&nostr, move |this, _nostr, event, cx| {
if event.signer_changed() && nip4e_enabled { if event.signer_changed() && settings.read(cx).is_nip4e_enabled(cx) {
this.get_announcement(cx); this.get_announcement(cx);
} }
}), }),

View File

@@ -35,7 +35,7 @@ pub struct PersonRegistry {
persons: HashMap<PublicKey, Entity<Person>>, persons: HashMap<PublicKey, Entity<Person>>,
/// Set of public keys that have been seen /// Set of public keys that have been seen
seens: RefCell<HashSet<PublicKey>>, seen: RefCell<HashSet<PublicKey>>,
/// Sender for requesting metadata /// Sender for requesting metadata
sender: flume::Sender<PublicKey>, sender: flume::Sender<PublicKey>,
@@ -62,22 +62,18 @@ impl PersonRegistry {
// Channel for communication between nostr and gpui // Channel for communication between nostr and gpui
let (tx, rx) = flume::bounded::<Dispatch>(100); let (tx, rx) = flume::bounded::<Dispatch>(100);
let (mta_tx, mta_rx) = flume::unbounded::<PublicKey>(); let (metadata_tx, metadata_rx) = flume::unbounded::<PublicKey>();
let mut tasks = smallvec![]; let mut tasks = smallvec![];
tasks.push(cx.background_spawn({ let client2 = client.clone();
let client = client.clone(); tasks.push(cx.background_spawn(async move {
async move { Self::handle_notifications(&client2, &tx).await;
Self::handle_notifications(&client, &tx).await;
}
})); }));
tasks.push(cx.background_spawn({ let client3 = client.clone();
let client = client.clone(); tasks.push(cx.background_spawn(async move {
async move { Self::handle_requests(&client3, &metadata_rx).await;
Self::handle_requests(&client, &mta_rx).await;
}
})); }));
tasks.push(cx.spawn(async move |this, cx| { tasks.push(cx.spawn(async move |this, cx| {
@@ -106,8 +102,8 @@ impl PersonRegistry {
Self { Self {
persons: HashMap::new(), persons: HashMap::new(),
seens: RefCell::new(HashSet::new()), seen: RefCell::new(HashSet::new()),
sender: mta_tx, sender: metadata_tx,
tasks, tasks,
} }
} }
@@ -210,7 +206,7 @@ impl PersonRegistry {
self.tasks.push(cx.spawn(async move |this, cx| { self.tasks.push(cx.spawn(async move |this, cx| {
if let Ok(persons) = task.await { if let Ok(persons) = task.await {
this.update(cx, |this, cx| { this.update(cx, |this, cx| {
this.bulk_inserts(persons, cx); this.bulk_insert(persons, cx);
}) })
.ok(); .ok();
} }
@@ -249,7 +245,7 @@ impl PersonRegistry {
} }
/// Insert batch of persons /// Insert batch of persons
fn bulk_inserts(&mut self, persons: Vec<Person>, cx: &mut Context<Self>) { fn bulk_insert(&mut self, persons: Vec<Person>, cx: &mut Context<Self>) {
for person in persons.into_iter() { for person in persons.into_iter() {
let public_key = person.public_key(); let public_key = person.public_key();
self.persons self.persons
@@ -284,7 +280,7 @@ impl PersonRegistry {
let public_key = *public_key; let public_key = *public_key;
if self.seens.borrow_mut().insert(public_key) { if self.seen.borrow_mut().insert(public_key) {
let sender = self.sender.clone(); let sender = self.sender.clone();
// Spawn background task to request metadata // Spawn background task to request metadata

View File

@@ -366,7 +366,7 @@ impl Workspace {
let chat = ChatRegistry::global(cx); let chat = ChatRegistry::global(cx);
// Trigger a refresh of the chat registry // Trigger a refresh of the chat registry
chat.update(cx, |this, cx| { chat.update(cx, |this, cx| {
this.refresh(cx); this.reload(cx);
}); });
} }
Command::ShowRelayList => { Command::ShowRelayList => {

View File

@@ -37,9 +37,9 @@ impl TrashPanel {
fn copy(&self, ix: usize, cx: &App) { fn copy(&self, ix: usize, cx: &App) {
let chat = ChatRegistry::global(cx); let chat = ChatRegistry::global(cx);
let trashes = chat.read(cx).trashes(); let trash_events = chat.read(cx).trash();
if let Some(message) = trashes.read(cx).iter().nth(ix) { if let Some(message) = trash_events.read(cx).iter().nth(ix) {
let item = ClipboardItem::new_string(message.raw_event.to_string()); let item = ClipboardItem::new_string(message.raw_event.to_string());
cx.write_to_clipboard(item); cx.write_to_clipboard(item);
} }
@@ -52,9 +52,9 @@ impl TrashPanel {
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> AnyElement { ) -> AnyElement {
let chat = ChatRegistry::global(cx); let chat = ChatRegistry::global(cx);
let trashes = chat.read(cx).trashes(); let trash_events = chat.read(cx).trash();
if let Some(message) = trashes.read(cx).iter().nth(ix) { if let Some(message) = trash_events.read(cx).iter().nth(ix) {
v_flex() v_flex()
.id(ix) .id(ix)
.p_2() .p_2()