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
+102 -91
View File
@@ -1,5 +1,5 @@
use anyhow::Error;
use gpui::{Context, Subscription, Task};
use gpui::{AppContext, Context, Subscription, Task};
use nostr_sdk::prelude::*;
use signed_core::{Announcement, RepoAddr, RepoStatus, filters};
@@ -33,20 +33,16 @@ impl RepoStore {
let subscription = cx.subscribe(&backend, |this, _backend, event, cx| {
let relevant = match event {
BackendEvent::NostrUpdate(update) => {
let coordinate = update.coordinate.as_ref() == Some(&this.addr.coordinate());
let author = update.author == this.addr.owner;
let coordinate = update.coordinate.as_ref() == Some(&this.addr);
let author = update.author == this.addr.public_key;
let kind = update.kind == Kind::GitRepoAnnouncement;
coordinate || (author && kind)
}
BackendEvent::Published(event) => {
let kind = event.kind == Kind::GitRepoAnnouncement;
let author = event.pubkey == this.addr.owner;
let coordinate = event
.tags
.coordinates()
.into_iter()
.any(|c| c == this.addr.coordinate());
let author = event.pubkey == this.addr.public_key;
let coordinate = event.tags.coordinates().into_iter().any(|c| c == this.addr);
coordinate || (kind && author)
}
@@ -103,7 +99,8 @@ impl RepoStore {
/// Re-query the local database and update all fields.
///
/// 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;
@@ -114,84 +111,99 @@ impl RepoStore {
let client = Backend::global(cx).read(cx).client();
let addr = self.addr.clone();
let task = cx.spawn(async move |this, cx| {
loop {
let queries = async {
let db = client.database();
let work = cx.background_spawn(async move {
let queries = async {
let db = client.database();
let announcements = db.query(filters::announcement(&addr)).await?;
let states = db.query(filters::state(&addr)).await?;
let activity = db.query(filters::activity(&addr)).await?;
let announcements = db.query(filters::announcement(&addr)).await?;
let states = db.query(filters::state(&addr)).await?;
let activity = db.query(filters::activity(&addr)).await?;
Ok::<_, Error>((announcements, states, activity))
}
.await;
Ok::<_, Error>((announcements, states, activity))
}
.await?;
let (announcements, states, activity) = match queries {
Ok(results) => results,
Err(e) => {
return this.update(cx, |this, cx| {
this.refreshing = false;
this.last_error = Some(e.to_string());
cx.notify();
});
}
};
let (announcements, states, activity) = queries;
let again = this.update(cx, |this, cx| {
this.announcement = latest(announcements)
.as_ref()
.and_then(Announcement::from_event);
// Parse and sort off the main thread; only plain data
// crosses back into the entity.
let announcement = latest(announcements)
.as_ref()
.and_then(Announcement::from_event);
if let Some(state) = latest(states) {
let (refs, head) = parse_state(&state);
this.refs = refs;
this.head = head;
}
let state = latest(states).map(|state| parse_state(&state));
this.issues.clear();
this.patches.clear();
this.pull_requests.clear();
this.statuses.clear();
let (mut issues, mut patches, mut pull_requests, mut statuses) =
(Vec::new(), Vec::new(), Vec::new(), Vec::new());
for event in activity {
match event.kind {
Kind::GitIssue => this.issues.push(event),
Kind::GitPatch => this.patches.push(event),
Kind::GitPullRequest | Kind::GitPullRequestUpdate => {
this.pull_requests.push(event)
}
kind if RepoStatus::from_kind(kind).is_some() => {
this.statuses.push(event)
}
_ => {}
}
}
sort_newest_first(&mut this.issues);
sort_newest_first(&mut this.patches);
sort_newest_first(&mut this.pull_requests);
cx.notify();
if this.refresh_dirty {
this.refresh_dirty = false;
true
} else {
this.refreshing = false;
false
}
})?;
if !again {
break;
for event in activity {
match event.kind {
Kind::GitIssue => issues.push(event),
Kind::GitPatch => patches.push(event),
Kind::GitPullRequest | Kind::GitPullRequestUpdate => pull_requests.push(event),
kind if RepoStatus::from_kind(kind).is_some() => statuses.push(event),
_ => {}
}
}
Ok(())
sort_newest_first(&mut issues);
sort_newest_first(&mut patches);
sort_newest_first(&mut pull_requests);
Ok::<_, Error>((
announcement,
state,
issues,
patches,
pull_requests,
statuses,
))
});
self.tasks.push(task);
self.tasks.push(cx.spawn(async move |this, cx| {
let (announcement, state, issues, patches, pull_requests, statuses) = match work.await {
Ok(data) => data,
Err(e) => {
return this.update(cx, |this, cx| {
this.refreshing = false;
this.last_error = Some(e.to_string());
cx.notify();
});
}
};
let again = this.update(cx, |this, cx| {
this.announcement = announcement;
if let Some((refs, head)) = state {
this.refs = refs;
this.head = head;
}
this.issues = issues;
this.patches = patches;
this.pull_requests = pull_requests;
this.statuses = statuses;
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(())
}));
}
/// Resolve the status of a root event (issue / patch / PR) per NIP-34.
@@ -213,7 +225,7 @@ impl RepoStore {
/// Open an issue on this repository.
pub fn open_issue(&mut self, subject: Option<String>, content: String, cx: &mut Context<Self>) {
let builder = GitIssue {
repository: self.addr.coordinate(),
repository: self.addr.clone(),
content,
subject,
labels: Vec::new(),
@@ -230,8 +242,8 @@ impl RepoStore {
};
let builder = EventBuilder::new(Kind::GitPatch, patch).tags([
Tag::coordinate(self.addr.coordinate(), None),
Tag::public_key(self.addr.owner),
Tag::coordinate(self.addr.clone(), None),
Tag::public_key(self.addr.public_key),
root_marker,
]);
@@ -246,9 +258,9 @@ impl RepoStore {
let builder = EventBuilder::new(status.kind(), "").tags([
root_ref,
Tag::public_key(self.addr.owner),
Tag::public_key(self.addr.public_key),
Tag::public_key(root.pubkey),
Tag::coordinate(self.addr.coordinate(), None),
Tag::coordinate(self.addr.clone(), None),
]);
self.send(builder, cx);
@@ -288,16 +300,15 @@ fn parse_state(event: &Event) -> (Vec<(String, String)>, Option<String>) {
let mut head = None;
for tag in event.tags.iter() {
let kind = tag.kind();
if kind == "HEAD" {
head = tag
.content()
.and_then(|v| v.strip_prefix("ref: refs/heads/"))
.map(str::to_owned);
} else if kind.starts_with("refs/")
&& let Some(commit) = tag.content()
{
refs.push((kind.to_owned(), commit.to_owned()));
match Nip34Tag::parse(tag.as_slice()) {
Ok(Nip34Tag::Head(branch)) => head = Some(branch),
Ok(Nip34Tag::RefHead { branch, commit }) => {
refs.push((format!("refs/heads/{branch}"), commit.to_string()));
}
Ok(Nip34Tag::RefTag { name, commit }) => {
refs.push((format!("refs/tags/{name}"), commit.to_string()));
}
_ => {}
}
}