This commit is contained in:
2026-08-06 16:00:00 +07:00
parent 0c6d700395
commit 640549a2c5
16 changed files with 426 additions and 438 deletions
+61 -53
View File
@@ -1,9 +1,9 @@
use std::collections::HashMap;
use anyhow::Error;
use gpui::{Context, Subscription, Task};
use gpui::{AppContext, Context, Subscription, Task};
use nostr_sdk::prelude::*;
use signed_core::{Announcement, filters};
use signed_core::{Announcement, RepoAddr, filters};
use crate::backend::{Backend, BackendEvent};
@@ -79,7 +79,8 @@ impl RepoListStore {
/// Re-query the local database. Latest announcement per repository wins.
///
/// Debounced: concurrent requests are coalesced into a single re-query
/// after the running one finishes.
/// after the running one finishes. The query and processing run on a
/// background thread; only the results are applied on the main thread.
pub fn refresh(&mut self, cx: &mut Context<Self>) {
if self.refreshing {
self.refresh_dirty = true;
@@ -90,63 +91,70 @@ impl RepoListStore {
let client = Backend::global(cx).read(cx).client();
let author = self.author;
let task = cx.spawn(async move |this, cx| {
loop {
let filter = match author {
Some(a) => filters::announcements_by(a),
None => filters::all_announcements(),
let work = cx.background_spawn(async move {
let filter = match author {
Some(a) => filters::announcements_by(a),
None => filters::all_announcements(),
};
let events = client.database().query(filter).await?;
// Dedup and sort off the main thread; only the final list
// crosses back into the entity.
let mut by_repo: HashMap<RepoAddr, Announcement> = HashMap::new();
for event in events {
let Some(announcement) = Announcement::from_event(&event) else {
continue;
};
let events = match client.database().query(filter).await {
Ok(events) => events,
Err(_) => {
return this.update(cx, |this, _cx| {
this.refreshing = false;
});
let addr = announcement.addr();
match by_repo.get(&addr) {
Some(existing) if existing.created_at >= announcement.created_at => {}
_ => {
by_repo.insert(addr, announcement);
}
};
let again = this.update(cx, |this, cx| {
let mut by_repo: HashMap<(String, String), Announcement> = HashMap::new();
for event in events {
let Some(announcement) = Announcement::from_event(&event) else {
continue;
};
let key = (announcement.owner.to_hex(), announcement.id.clone());
match by_repo.get(&key) {
Some(existing) if existing.created_at >= announcement.created_at => {}
_ => {
by_repo.insert(key, announcement);
}
}
}
let mut announcements: Vec<Announcement> = by_repo.into_values().collect();
announcements.sort_by_key(|a| std::cmp::Reverse(a.created_at));
this.announcements = announcements;
cx.notify();
if this.refresh_dirty {
this.refresh_dirty = false;
true
} else {
this.refreshing = false;
false
}
})?;
if !again {
break;
}
}
Ok(())
let mut announcements: Vec<Announcement> = by_repo.into_values().collect();
announcements.sort_by_key(|a| std::cmp::Reverse(a.created_at));
Ok::<_, Error>(announcements)
});
self.tasks.push(task);
self.tasks.push(cx.spawn(async move |this, cx| {
let announcements = match work.await {
Ok(announcements) => announcements,
// Database errors are transient; keep the last list.
Err(_) => {
return this.update(cx, |this, _cx| {
this.refreshing = false;
});
}
};
let again = this.update(cx, |this, cx| {
this.announcements = announcements;
cx.notify();
this.refreshing = false;
if this.refresh_dirty {
this.refresh_dirty = false;
true
} else {
false
}
})?;
// Requests that arrived while the refresh was running are
// coalesced into one follow-up refresh.
if again {
this.update(cx, |this, cx| this.refresh(cx))?;
}
Ok(())
}));
}
}