From 2d9563b888ee0d6f2632197cf291e7329ef31644 Mon Sep 17 00:00:00 2001 From: reya Date: Tue, 6 Jan 2026 20:17:49 +0700 Subject: [PATCH] refactor person registry --- Cargo.lock | 2 +- crates/chat/src/room.rs | 4 +- crates/chat_ui/src/lib.rs | 6 +- crates/chat_ui/src/text.rs | 2 +- crates/coop/src/chatspace.rs | 4 +- crates/coop/src/user/viewer.rs | 2 +- crates/coop/src/views/compose.rs | 2 +- crates/coop/src/views/screening.rs | 2 +- crates/coop/src/views/startup.rs | 4 +- crates/person/Cargo.toml | 2 +- crates/person/src/lib.rs | 115 +++++++++++++++-------------- crates/relay_auth/src/lib.rs | 9 +-- 12 files changed, 77 insertions(+), 77 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f363450..03c7dca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4467,8 +4467,8 @@ version = "0.3.0" dependencies = [ "anyhow", "common", + "flume", "gpui", - "itertools 0.13.0", "log", "nostr-sdk", "smallvec", diff --git a/crates/chat/src/room.rs b/crates/chat/src/room.rs index 24096dd..a8daf18 100644 --- a/crates/chat/src/room.rs +++ b/crates/chat/src/room.rs @@ -278,7 +278,7 @@ impl Room { .or_else(|| self.members.first()) .expect("Room should have at least one member"); - persons.read(cx).get_person(target_member, cx) + persons.read(cx).get(target_member, cx) } /// Merge the names of the first two members of the room. @@ -289,7 +289,7 @@ impl Room { let profiles: Vec = self .members .iter() - .map(|public_key| persons.read(cx).get_person(public_key, cx)) + .map(|public_key| persons.read(cx).get(public_key, cx)) .collect(); let mut name = profiles diff --git a/crates/chat_ui/src/lib.rs b/crates/chat_ui/src/lib.rs index 96058c3..1d9753c 100644 --- a/crates/chat_ui/src/lib.rs +++ b/crates/chat_ui/src/lib.rs @@ -508,7 +508,7 @@ impl ChatPanel { fn profile(&self, public_key: &PublicKey, cx: &Context) -> Profile { let persons = PersonRegistry::global(cx); - persons.read(cx).get_person(public_key, cx) + persons.read(cx).get(public_key, cx) } fn render_announcement(&self, ix: usize, cx: &Context) -> AnyElement { @@ -795,7 +795,7 @@ impl ChatPanel { fn render_report(report: &SendReport, cx: &App) -> impl IntoElement { let persons = PersonRegistry::global(cx); - let profile = persons.read(cx).get_person(&report.receiver, cx); + let profile = persons.read(cx).get(&report.receiver, cx); let name = profile.display_name(); let avatar = profile.avatar(true); @@ -1057,7 +1057,7 @@ impl ChatPanel { fn render_reply(&self, id: &EventId, cx: &Context) -> impl IntoElement { if let Some(text) = self.message(id) { let persons = PersonRegistry::global(cx); - let profile = persons.read(cx).get_person(&text.author, cx); + let profile = persons.read(cx).get(&text.author, cx); div() .w_full() diff --git a/crates/chat_ui/src/text.rs b/crates/chat_ui/src/text.rs index cd6df79..77e0dbb 100644 --- a/crates/chat_ui/src/text.rs +++ b/crates/chat_ui/src/text.rs @@ -254,7 +254,7 @@ fn render_pubkey( cx: &App, ) { let persons = PersonRegistry::global(cx); - let profile = persons.read(cx).get_person(&public_key, cx); + let profile = persons.read(cx).get(&public_key, cx); let display_name = format!("@{}", profile.display_name()); text.replace_range(range.clone(), &display_name); diff --git a/crates/coop/src/chatspace.rs b/crates/coop/src/chatspace.rs index b5fa143..168806d 100644 --- a/crates/coop/src/chatspace.rs +++ b/crates/coop/src/chatspace.rs @@ -259,7 +259,7 @@ impl ChatSpace { match result { Ok(profile) => { persons.update(cx, |this, cx| { - this.insert_or_update_person(profile, cx); + this.insert(profile, cx); // Close the edit profile modal window.close_all_modals(cx); }); @@ -545,7 +545,7 @@ impl ChatSpace { }) .when_some(identity.read(cx).option_public_key(), |this, public_key| { let persons = PersonRegistry::global(cx); - let profile = persons.read(cx).get_person(&public_key, cx); + let profile = persons.read(cx).get(&public_key, cx); let keystore = KeyStore::global(cx); let is_using_file_keystore = keystore.read(cx).is_using_file_keystore(); diff --git a/crates/coop/src/user/viewer.rs b/crates/coop/src/user/viewer.rs index a88029d..1abfc17 100644 --- a/crates/coop/src/user/viewer.rs +++ b/crates/coop/src/user/viewer.rs @@ -44,7 +44,7 @@ impl ProfileViewer { let client = nostr.read(cx).client(); let persons = PersonRegistry::global(cx); - let profile = persons.read(cx).get_person(&target, cx); + let profile = persons.read(cx).get(&target, cx); let mut tasks = smallvec![]; diff --git a/crates/coop/src/views/compose.rs b/crates/coop/src/views/compose.rs index df55247..9742bd7 100644 --- a/crates/coop/src/views/compose.rs +++ b/crates/coop/src/views/compose.rs @@ -369,7 +369,7 @@ impl Compose { }; let public_key = contact.public_key; - let profile = persons.read(cx).get_person(&public_key, cx); + let profile = persons.read(cx).get(&public_key, cx); items.push( h_flex() diff --git a/crates/coop/src/views/screening.rs b/crates/coop/src/views/screening.rs index c5482ed..bc9a8fd 100644 --- a/crates/coop/src/views/screening.rs +++ b/crates/coop/src/views/screening.rs @@ -37,7 +37,7 @@ impl Screening { let client = nostr.read(cx).client(); let persons = PersonRegistry::global(cx); - let profile = persons.read(cx).get_person(&public_key, cx); + let profile = persons.read(cx).get(&public_key, cx); let mut tasks = smallvec![]; diff --git a/crates/coop/src/views/startup.rs b/crates/coop/src/views/startup.rs index b67db15..b83fd48 100644 --- a/crates/coop/src/views/startup.rs +++ b/crates/coop/src/views/startup.rs @@ -205,9 +205,7 @@ impl Render for Startup { fn render(&mut self, _window: &mut gpui::Window, cx: &mut Context) -> impl IntoElement { let persons = PersonRegistry::global(cx); let bunker = self.credential.secret().starts_with("bunker://"); - let profile = persons - .read(cx) - .get_person(&self.credential.public_key(), cx); + let profile = persons.read(cx).get(&self.credential.public_key(), cx); v_flex() .image_cache(self.image_cache.clone()) diff --git a/crates/person/Cargo.toml b/crates/person/Cargo.toml index c3804c3..f0239bf 100644 --- a/crates/person/Cargo.toml +++ b/crates/person/Cargo.toml @@ -11,7 +11,7 @@ state = { path = "../state" } gpui.workspace = true nostr-sdk.workspace = true anyhow.workspace = true -itertools.workspace = true smallvec.workspace = true smol.workspace = true +flume.workspace = true log.workspace = true diff --git a/crates/person/src/lib.rs b/crates/person/src/lib.rs index 5e24ffc..42cb957 100644 --- a/crates/person/src/lib.rs +++ b/crates/person/src/lib.rs @@ -17,64 +17,50 @@ impl Global for GlobalPersonRegistry {} #[derive(Debug)] pub struct PersonRegistry { /// Collection of all persons (user profiles) - pub persons: HashMap>, + persons: HashMap>, /// Tasks for asynchronous operations - _tasks: SmallVec<[Task<()>; 2]>, + _tasks: SmallVec<[Task<()>; 3]>, } impl PersonRegistry { - /// Retrieve the global person registry state + /// Retrieve the global person registry pub fn global(cx: &App) -> Entity { cx.global::().0.clone() } /// Set the global person registry instance - pub(crate) fn set_global(state: Entity, cx: &mut App) { + fn set_global(state: Entity, cx: &mut App) { cx.set_global(GlobalPersonRegistry(state)); } /// Create a new person registry instance - pub(crate) fn new(cx: &mut Context) -> Self { + fn new(cx: &mut Context) -> Self { let nostr = NostrRegistry::global(cx); let client = nostr.read(cx).client(); + + // Channel for communication between nostr and gpui + let (tx, rx) = flume::bounded::(100); + let mut tasks = smallvec![]; tasks.push( - // Handle notifications - cx.spawn({ - let client = nostr.read(cx).client(); + // Handle nostr notifications + cx.background_spawn({ + let client = client.clone(); - async move |this, cx| { - let mut notifications = client.notifications(); - let mut processed_events = HashSet::new(); + async move { Self::handle_notifications(&client, &tx).await } + }), + ); - while let Ok(notification) = notifications.recv().await { - let RelayPoolNotification::Message { message, .. } = notification else { - // Skip if the notification is not a message - continue; - }; - - if let RelayMessage::Event { event, .. } = message { - if !processed_events.insert(event.id) { - // Skip if the event has already been processed - continue; - } - - if event.kind != Kind::Metadata { - // Skip if the event is not a metadata event - continue; - }; - - let metadata = Metadata::from_json(&event.content).unwrap_or_default(); - let profile = Profile::new(event.pubkey, metadata); - - this.update(cx, |this, cx| { - this.insert_or_update_person(profile, cx); - }) - .expect("Entity has been released") - } - } + tasks.push( + // Update GPUI state + cx.spawn(async move |this, cx| { + while let Ok(profile) = rx.recv_async().await { + this.update(cx, |this, cx| { + this.insert(profile, cx); + }) + .ok(); } }), ); @@ -83,18 +69,19 @@ impl PersonRegistry { // Load all user profiles from the database cx.spawn(async move |this, cx| { let result = cx - .background_spawn(async move { Self::load_persons(&client).await }) + .background_executor() + .await_on_background(async move { Self::load_persons(&client).await }) .await; match result { Ok(profiles) => { this.update(cx, |this, cx| { - this.bulk_insert_persons(profiles, cx); + this.bulk_inserts(profiles, cx); }) .ok(); } Err(e) => { - log::error!("Failed to load persons: {e}"); + log::error!("Failed to load all persons from the database: {e}"); } }; }), @@ -106,6 +93,34 @@ impl PersonRegistry { } } + /// Handle nostr notifications + async fn handle_notifications(client: &Client, tx: &flume::Sender) { + let mut notifications = client.notifications(); + let mut processed_events = HashSet::new(); + + while let Ok(notification) = notifications.recv().await { + let RelayPoolNotification::Message { message, .. } = notification else { + // Skip if the notification is not a message + continue; + }; + + if let RelayMessage::Event { event, .. } = message { + if !processed_events.insert(event.id) { + // Skip if the event has already been processed + continue; + } + + // Only process metadata events + if event.kind == Kind::Metadata { + let metadata = Metadata::from_json(&event.content).unwrap_or_default(); + let profile = Profile::new(event.pubkey, metadata); + + tx.send_async(profile).await.ok(); + }; + } + } + } + /// Load all user profiles from the database async fn load_persons(client: &Client) -> Result, Error> { let filter = Filter::new().kind(Kind::Metadata).limit(200); @@ -123,7 +138,7 @@ impl PersonRegistry { } /// Insert batch of persons - fn bulk_insert_persons(&mut self, profiles: Vec, cx: &mut Context) { + fn bulk_inserts(&mut self, profiles: Vec, cx: &mut Context) { for profile in profiles.into_iter() { self.persons .insert(profile.public_key(), cx.new(|_| profile)); @@ -132,7 +147,7 @@ impl PersonRegistry { } /// Insert or update a person - pub fn insert_or_update_person(&mut self, profile: Profile, cx: &mut App) { + pub fn insert(&mut self, profile: Profile, cx: &mut App) { let public_key = profile.public_key(); match self.persons.get(&public_key) { @@ -148,24 +163,12 @@ impl PersonRegistry { } } - /// Get single person - pub fn get_person(&self, public_key: &PublicKey, cx: &App) -> Profile { + /// Get single person by public key + pub fn get(&self, public_key: &PublicKey, cx: &App) -> Profile { self.persons .get(public_key) .map(|e| e.read(cx)) .cloned() .unwrap_or(Profile::new(public_key.to_owned(), Metadata::default())) } - - /// Get group of persons - pub fn get_group_person(&self, public_keys: &[PublicKey], cx: &App) -> Vec { - let mut profiles = vec![]; - - for public_key in public_keys.iter() { - let profile = self.get_person(public_key, cx); - profiles.push(profile); - } - - profiles - } } diff --git a/crates/relay_auth/src/lib.rs b/crates/relay_auth/src/lib.rs index 1c25f04..9fcb681 100644 --- a/crates/relay_auth/src/lib.rs +++ b/crates/relay_auth/src/lib.rs @@ -313,12 +313,13 @@ impl RelayAuth { move |_ev, window, cx| { // Set loading state to true loading.set(true); + // Process to approve the request entity .update(cx, |this, cx| { this.response(req.clone(), window, cx); }) - .expect("Entity has been released"); + .ok(); } }) }); @@ -326,9 +327,7 @@ impl RelayAuth { // Push the notification to the current window window.push_notification(note, cx); - // Focus the window if it's not active - if !window.is_window_hovered() { - window.activate_window(); - } + // Bring the window to the front + cx.activate(true); } }