refactor 2

This commit is contained in:
2026-08-06 16:26:37 +07:00
parent 640549a2c5
commit 63f2de70e1
7 changed files with 406 additions and 31 deletions
+32 -24
View File
@@ -1,10 +1,16 @@
use std::time::Duration;
use anyhow::Error;
use gpui::{AppContext, Context, Subscription, Task};
use nostr_sdk::prelude::*;
use signed_core::{Announcement, RepoAddr, RepoStatus, filters};
use signed_core::{Announcement, RepoAddr, RepoStatus, filters, parse_state};
use crate::backend::{Backend, BackendEvent};
/// Delay between a refresh request and the actual re-query, so bursts of
/// events (e.g. per-event `NostrUpdate`s) collapse into one query.
const REFRESH_DEBOUNCE: Duration = Duration::from_millis(300);
/// Per-repository store: announcement, state, issues, patches, PRs and
/// their resolved statuses. Always derived from the local database.
pub struct RepoStore {
@@ -22,6 +28,8 @@ pub struct RepoStore {
pub last_error: Option<String>,
refreshing: bool,
refresh_dirty: bool,
/// A refresh is waiting out [`REFRESH_DEBOUNCE`].
debouncing: bool,
tasks: Vec<Task<Result<(), Error>>>,
_subscription: Subscription,
}
@@ -66,6 +74,7 @@ impl RepoStore {
last_error: None,
refreshing: false,
refresh_dirty: false,
debouncing: false,
_subscription: subscription,
tasks: Vec::new(),
};
@@ -98,14 +107,34 @@ 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. The query and processing run on a
/// Debounced: a short delay collapses bursts of requests (e.g. per-event
/// `NostrUpdate`s), and requests that arrive while a query is running are
/// folded into one follow-up query. 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;
return;
}
if self.debouncing {
return;
}
self.debouncing = true;
let task = cx.spawn(async move |this, cx| {
cx.background_executor().timer(REFRESH_DEBOUNCE).await;
this.update(cx, |this, cx| {
this.debouncing = false;
this.run_refresh(cx);
})
});
self.tasks.push(task);
}
/// One query + apply cycle (debounced entry point).
fn run_refresh(&mut self, cx: &mut Context<Self>) {
self.refreshing = true;
let client = Backend::global(cx).read(cx).client();
@@ -293,24 +322,3 @@ fn latest(events: Events) -> Option<Event> {
fn sort_newest_first(events: &mut [Event]) {
events.sort_by_key(|e| std::cmp::Reverse(e.created_at));
}
/// Parse a kind `30618` state event into refs and HEAD.
fn parse_state(event: &Event) -> (Vec<(String, String)>, Option<String>) {
let mut refs = Vec::new();
let mut head = None;
for tag in event.tags.iter() {
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()));
}
_ => {}
}
}
(refs, head)
}
+36 -6
View File
@@ -1,4 +1,6 @@
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use anyhow::Error;
use gpui::{AppContext, Context, Subscription, Task};
@@ -7,12 +9,19 @@ use signed_core::{Announcement, RepoAddr, filters};
use crate::backend::{Backend, BackendEvent};
/// Delay between a refresh request and the actual re-query, so bursts of
/// events (e.g. sync progress ticks) collapse into one query.
const REFRESH_DEBOUNCE: Duration = Duration::from_millis(300);
/// Store listing repository announcements (global discovery or per-author).
pub struct RepoListStore {
pub announcements: Vec<Announcement>,
/// Shared so views can clone the list per frame without a deep copy.
pub announcements: Arc<Vec<Announcement>>,
author: Option<PublicKey>,
refreshing: bool,
refresh_dirty: bool,
/// A refresh is waiting out [`REFRESH_DEBOUNCE`].
debouncing: bool,
tasks: Vec<Task<Result<(), Error>>>,
_subscription: Subscription,
}
@@ -42,10 +51,11 @@ impl RepoListStore {
});
let mut store = Self {
announcements: Vec::new(),
announcements: Arc::new(Vec::new()),
author,
refreshing: false,
refresh_dirty: false,
debouncing: false,
_subscription: subscription,
tasks: Vec::new(),
};
@@ -78,14 +88,34 @@ 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. The query and processing run on a
/// background thread; only the results are applied on the main thread.
/// Debounced: a short delay collapses bursts of requests (e.g. sync
/// progress ticks), and requests that arrive while a query is running
/// are folded into one follow-up query. 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;
return;
}
if self.debouncing {
return;
}
self.debouncing = true;
let task = cx.spawn(async move |this, cx| {
cx.background_executor().timer(REFRESH_DEBOUNCE).await;
this.update(cx, |this, cx| {
this.debouncing = false;
this.run_refresh(cx);
})
});
self.tasks.push(task);
}
/// One query + apply cycle (debounced entry point).
fn run_refresh(&mut self, cx: &mut Context<Self>) {
self.refreshing = true;
let client = Backend::global(cx).read(cx).client();
@@ -136,7 +166,7 @@ impl RepoListStore {
};
let again = this.update(cx, |this, cx| {
this.announcements = announcements;
this.announcements = Arc::new(announcements);
cx.notify();
this.refreshing = false;