refactor person registry

This commit is contained in:
2026-01-06 20:17:49 +07:00
parent 0072c5255d
commit 2d9563b888
12 changed files with 77 additions and 77 deletions

2
Cargo.lock generated
View File

@@ -4467,8 +4467,8 @@ version = "0.3.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"common", "common",
"flume",
"gpui", "gpui",
"itertools 0.13.0",
"log", "log",
"nostr-sdk", "nostr-sdk",
"smallvec", "smallvec",

View File

@@ -278,7 +278,7 @@ impl Room {
.or_else(|| self.members.first()) .or_else(|| self.members.first())
.expect("Room should have at least one member"); .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. /// Merge the names of the first two members of the room.
@@ -289,7 +289,7 @@ impl Room {
let profiles: Vec<Profile> = self let profiles: Vec<Profile> = self
.members .members
.iter() .iter()
.map(|public_key| persons.read(cx).get_person(public_key, cx)) .map(|public_key| persons.read(cx).get(public_key, cx))
.collect(); .collect();
let mut name = profiles let mut name = profiles

View File

@@ -508,7 +508,7 @@ impl ChatPanel {
fn profile(&self, public_key: &PublicKey, cx: &Context<Self>) -> Profile { fn profile(&self, public_key: &PublicKey, cx: &Context<Self>) -> Profile {
let persons = PersonRegistry::global(cx); 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<Self>) -> AnyElement { fn render_announcement(&self, ix: usize, cx: &Context<Self>) -> AnyElement {
@@ -795,7 +795,7 @@ impl ChatPanel {
fn render_report(report: &SendReport, cx: &App) -> impl IntoElement { fn render_report(report: &SendReport, cx: &App) -> impl IntoElement {
let persons = PersonRegistry::global(cx); 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 name = profile.display_name();
let avatar = profile.avatar(true); let avatar = profile.avatar(true);
@@ -1057,7 +1057,7 @@ impl ChatPanel {
fn render_reply(&self, id: &EventId, cx: &Context<Self>) -> impl IntoElement { fn render_reply(&self, id: &EventId, cx: &Context<Self>) -> impl IntoElement {
if let Some(text) = self.message(id) { if let Some(text) = self.message(id) {
let persons = PersonRegistry::global(cx); 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() div()
.w_full() .w_full()

View File

@@ -254,7 +254,7 @@ fn render_pubkey(
cx: &App, cx: &App,
) { ) {
let persons = PersonRegistry::global(cx); 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()); let display_name = format!("@{}", profile.display_name());
text.replace_range(range.clone(), &display_name); text.replace_range(range.clone(), &display_name);

View File

@@ -259,7 +259,7 @@ impl ChatSpace {
match result { match result {
Ok(profile) => { Ok(profile) => {
persons.update(cx, |this, cx| { persons.update(cx, |this, cx| {
this.insert_or_update_person(profile, cx); this.insert(profile, cx);
// Close the edit profile modal // Close the edit profile modal
window.close_all_modals(cx); window.close_all_modals(cx);
}); });
@@ -545,7 +545,7 @@ impl ChatSpace {
}) })
.when_some(identity.read(cx).option_public_key(), |this, public_key| { .when_some(identity.read(cx).option_public_key(), |this, public_key| {
let persons = PersonRegistry::global(cx); 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 keystore = KeyStore::global(cx);
let is_using_file_keystore = keystore.read(cx).is_using_file_keystore(); let is_using_file_keystore = keystore.read(cx).is_using_file_keystore();

View File

@@ -44,7 +44,7 @@ impl ProfileViewer {
let client = nostr.read(cx).client(); let client = nostr.read(cx).client();
let persons = PersonRegistry::global(cx); 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![]; let mut tasks = smallvec![];

View File

@@ -369,7 +369,7 @@ impl Compose {
}; };
let public_key = contact.public_key; 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( items.push(
h_flex() h_flex()

View File

@@ -37,7 +37,7 @@ impl Screening {
let client = nostr.read(cx).client(); let client = nostr.read(cx).client();
let persons = PersonRegistry::global(cx); 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![]; let mut tasks = smallvec![];

View File

@@ -205,9 +205,7 @@ impl Render for Startup {
fn render(&mut self, _window: &mut gpui::Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, _window: &mut gpui::Window, cx: &mut Context<Self>) -> impl IntoElement {
let persons = PersonRegistry::global(cx); let persons = PersonRegistry::global(cx);
let bunker = self.credential.secret().starts_with("bunker://"); let bunker = self.credential.secret().starts_with("bunker://");
let profile = persons let profile = persons.read(cx).get(&self.credential.public_key(), cx);
.read(cx)
.get_person(&self.credential.public_key(), cx);
v_flex() v_flex()
.image_cache(self.image_cache.clone()) .image_cache(self.image_cache.clone())

View File

@@ -11,7 +11,7 @@ state = { path = "../state" }
gpui.workspace = true gpui.workspace = true
nostr-sdk.workspace = true nostr-sdk.workspace = true
anyhow.workspace = true anyhow.workspace = true
itertools.workspace = true
smallvec.workspace = true smallvec.workspace = true
smol.workspace = true smol.workspace = true
flume.workspace = true
log.workspace = true log.workspace = true

View File

@@ -17,64 +17,50 @@ impl Global for GlobalPersonRegistry {}
#[derive(Debug)] #[derive(Debug)]
pub struct PersonRegistry { pub struct PersonRegistry {
/// Collection of all persons (user profiles) /// Collection of all persons (user profiles)
pub persons: HashMap<PublicKey, Entity<Profile>>, persons: HashMap<PublicKey, Entity<Profile>>,
/// Tasks for asynchronous operations /// Tasks for asynchronous operations
_tasks: SmallVec<[Task<()>; 2]>, _tasks: SmallVec<[Task<()>; 3]>,
} }
impl PersonRegistry { impl PersonRegistry {
/// Retrieve the global person registry state /// Retrieve the global person registry
pub fn global(cx: &App) -> Entity<Self> { pub fn global(cx: &App) -> Entity<Self> {
cx.global::<GlobalPersonRegistry>().0.clone() cx.global::<GlobalPersonRegistry>().0.clone()
} }
/// Set the global person registry instance /// Set the global person registry instance
pub(crate) fn set_global(state: Entity<Self>, cx: &mut App) { fn set_global(state: Entity<Self>, cx: &mut App) {
cx.set_global(GlobalPersonRegistry(state)); cx.set_global(GlobalPersonRegistry(state));
} }
/// Create a new person registry instance /// Create a new person registry instance
pub(crate) fn new(cx: &mut Context<Self>) -> Self { fn new(cx: &mut Context<Self>) -> Self {
let nostr = NostrRegistry::global(cx); let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client(); let client = nostr.read(cx).client();
// Channel for communication between nostr and gpui
let (tx, rx) = flume::bounded::<Profile>(100);
let mut tasks = smallvec![]; let mut tasks = smallvec![];
tasks.push( tasks.push(
// Handle notifications // Handle nostr notifications
cx.spawn({ cx.background_spawn({
let client = nostr.read(cx).client(); let client = client.clone();
async move |this, cx| { async move { Self::handle_notifications(&client, &tx).await }
let mut notifications = client.notifications(); }),
let mut processed_events = HashSet::new(); );
while let Ok(notification) = notifications.recv().await { tasks.push(
let RelayPoolNotification::Message { message, .. } = notification else { // Update GPUI state
// Skip if the notification is not a message cx.spawn(async move |this, cx| {
continue; while let Ok(profile) = rx.recv_async().await {
}; this.update(cx, |this, cx| {
this.insert(profile, cx);
if let RelayMessage::Event { event, .. } = message { })
if !processed_events.insert(event.id) { .ok();
// 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")
}
}
} }
}), }),
); );
@@ -83,18 +69,19 @@ impl PersonRegistry {
// Load all user profiles from the database // Load all user profiles from the database
cx.spawn(async move |this, cx| { cx.spawn(async move |this, cx| {
let result = 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; .await;
match result { match result {
Ok(profiles) => { Ok(profiles) => {
this.update(cx, |this, cx| { this.update(cx, |this, cx| {
this.bulk_insert_persons(profiles, cx); this.bulk_inserts(profiles, cx);
}) })
.ok(); .ok();
} }
Err(e) => { 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<Profile>) {
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 /// Load all user profiles from the database
async fn load_persons(client: &Client) -> Result<Vec<Profile>, Error> { async fn load_persons(client: &Client) -> Result<Vec<Profile>, Error> {
let filter = Filter::new().kind(Kind::Metadata).limit(200); let filter = Filter::new().kind(Kind::Metadata).limit(200);
@@ -123,7 +138,7 @@ impl PersonRegistry {
} }
/// Insert batch of persons /// Insert batch of persons
fn bulk_insert_persons(&mut self, profiles: Vec<Profile>, cx: &mut Context<Self>) { fn bulk_inserts(&mut self, profiles: Vec<Profile>, cx: &mut Context<Self>) {
for profile in profiles.into_iter() { for profile in profiles.into_iter() {
self.persons self.persons
.insert(profile.public_key(), cx.new(|_| profile)); .insert(profile.public_key(), cx.new(|_| profile));
@@ -132,7 +147,7 @@ impl PersonRegistry {
} }
/// Insert or update a person /// 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(); let public_key = profile.public_key();
match self.persons.get(&public_key) { match self.persons.get(&public_key) {
@@ -148,24 +163,12 @@ impl PersonRegistry {
} }
} }
/// Get single person /// Get single person by public key
pub fn get_person(&self, public_key: &PublicKey, cx: &App) -> Profile { pub fn get(&self, public_key: &PublicKey, cx: &App) -> Profile {
self.persons self.persons
.get(public_key) .get(public_key)
.map(|e| e.read(cx)) .map(|e| e.read(cx))
.cloned() .cloned()
.unwrap_or(Profile::new(public_key.to_owned(), Metadata::default())) .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<Profile> {
let mut profiles = vec![];
for public_key in public_keys.iter() {
let profile = self.get_person(public_key, cx);
profiles.push(profile);
}
profiles
}
} }

View File

@@ -313,12 +313,13 @@ impl RelayAuth {
move |_ev, window, cx| { move |_ev, window, cx| {
// Set loading state to true // Set loading state to true
loading.set(true); loading.set(true);
// Process to approve the request // Process to approve the request
entity entity
.update(cx, |this, cx| { .update(cx, |this, cx| {
this.response(req.clone(), window, 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 // Push the notification to the current window
window.push_notification(note, cx); window.push_notification(note, cx);
// Focus the window if it's not active // Bring the window to the front
if !window.is_window_hovered() { cx.activate(true);
window.activate_window();
}
} }
} }