clean up
This commit is contained in:
Generated
-1
@@ -6815,7 +6815,6 @@ name = "signed_nostr"
|
|||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"flume 0.11.1",
|
|
||||||
"nostr",
|
"nostr",
|
||||||
"nostr-connect",
|
"nostr-connect",
|
||||||
"nostr-gossip-memory",
|
"nostr-gossip-memory",
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ nostr-sdk.workspace = true
|
|||||||
nostr-connect.workspace = true
|
nostr-connect.workspace = true
|
||||||
nostr-gossip-memory.workspace = true
|
nostr-gossip-memory.workspace = true
|
||||||
|
|
||||||
flume.workspace = true
|
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
webbrowser.workspace = true
|
webbrowser.workspace = true
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
mod backend;
|
mod backend;
|
||||||
pub mod pump;
|
|
||||||
mod signer;
|
mod signer;
|
||||||
|
mod update;
|
||||||
|
|
||||||
pub use backend::NostrBackend;
|
pub use backend::NostrBackend;
|
||||||
pub use pump::Update;
|
|
||||||
pub use signer::{SignedAuthUrlHandler, UniversalSigner};
|
pub use signer::{SignedAuthUrlHandler, UniversalSigner};
|
||||||
|
pub use update::Update;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use flume::Sender;
|
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
|
|
||||||
/// A lightweight "something changed" signal for the UI.
|
/// A lightweight "something changed" signal for the UI.
|
||||||
@@ -13,18 +12,9 @@ pub struct Update {
|
|||||||
pub event_id: EventId,
|
pub event_id: EventId,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Consume the client notification stream and forward [`Update`]s into `tx`
|
impl Update {
|
||||||
/// until the receiving end is dropped or the client shuts down.
|
/// Build an update from a received event.
|
||||||
///
|
pub fn from_event(event: &Event) -> Self {
|
||||||
/// Spawn this once, e.g. inside `cx.background_spawn`.
|
|
||||||
pub async fn run(client: Client, tx: Sender<Update>) {
|
|
||||||
let mut notifications = client.notifications();
|
|
||||||
|
|
||||||
while let Some(notification) = notifications.next().await {
|
|
||||||
let ClientNotification::Event { event, .. } = notification else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
let coordinate = event
|
let coordinate = event
|
||||||
.tags
|
.tags
|
||||||
.iter()
|
.iter()
|
||||||
@@ -32,15 +22,11 @@ pub async fn run(client: Client, tx: Sender<Update>) {
|
|||||||
.and_then(|t| t.content())
|
.and_then(|t| t.content())
|
||||||
.map(str::to_owned);
|
.map(str::to_owned);
|
||||||
|
|
||||||
let update = Update {
|
Self {
|
||||||
kind: event.kind,
|
kind: event.kind,
|
||||||
coordinate,
|
coordinate,
|
||||||
author: event.pubkey,
|
author: event.pubkey,
|
||||||
event_id: event.id,
|
event_id: event.id,
|
||||||
};
|
|
||||||
|
|
||||||
if tx.send_async(update).await.is_err() {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
use anyhow::Error;
|
use anyhow::{Error, anyhow};
|
||||||
use gpui::{App, AppContext, Context, Entity, EventEmitter, Global, Task};
|
use gpui::{App, AppContext, Context, Entity, EventEmitter, Global, Task};
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use signed_nostr::{NostrBackend, UniversalSigner, pump::Update};
|
use signed_nostr::{NostrBackend, UniversalSigner, Update};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum BackendEvent {
|
pub enum BackendEvent {
|
||||||
@@ -54,26 +54,33 @@ impl Backend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn new(inner: NostrBackend, cx: &mut Context<Self>) -> Self {
|
pub(crate) fn new(inner: NostrBackend, cx: &mut Context<Self>) -> Self {
|
||||||
// Pump: relays -> LMDB (automatic) -> flume -> BackendEvent::NostrUpdate.
|
|
||||||
let (tx, rx) = flume::bounded::<Update>(4096);
|
|
||||||
let client = inner.client();
|
let client = inner.client();
|
||||||
|
|
||||||
let pump = cx.background_spawn(async move {
|
let pump = cx.spawn(async move |this, cx| {
|
||||||
signed_nostr::pump::run(client, tx).await;
|
let mut notifications = client.notifications();
|
||||||
Ok(())
|
|
||||||
});
|
|
||||||
|
|
||||||
let forward = cx.spawn(async move |this, cx| {
|
while let Some(notification) = notifications.next().await {
|
||||||
while let Ok(update) = rx.recv_async().await {
|
let ClientNotification::Event { event, .. } = notification else {
|
||||||
this.update(cx, |_, cx| cx.emit(BackendEvent::NostrUpdate(update)))?;
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let update = Update::from_event(&event);
|
||||||
|
|
||||||
|
if this
|
||||||
|
.update(cx, |_, cx| cx.emit(BackendEvent::NostrUpdate(update)))
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
inner,
|
inner,
|
||||||
current_user: None,
|
current_user: None,
|
||||||
tasks: vec![pump, forward],
|
tasks: vec![pump],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,9 +148,7 @@ impl Backend {
|
|||||||
this.update(cx, |_this, cx| cx.emit(BackendEvent::Connected))?;
|
this.update(cx, |_this, cx| cx.emit(BackendEvent::Connected))?;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
this.update(cx, |_this, cx| {
|
this.update(cx, |_this, cx| cx.emit(BackendEvent::error(e.to_string())))?;
|
||||||
cx.emit(BackendEvent::error(e.to_string()))
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -155,15 +160,11 @@ impl Backend {
|
|||||||
pub fn subscribe(&mut self, filter: Filter, cx: &mut Context<Self>) {
|
pub fn subscribe(&mut self, filter: Filter, cx: &mut Context<Self>) {
|
||||||
let backend = self.inner.clone();
|
let backend = self.inner.clone();
|
||||||
|
|
||||||
let task = cx.background_spawn(async move {
|
let task = cx.background_spawn(async move { backend.subscribe(filter).await.map(|_| ()) });
|
||||||
backend.subscribe(filter).await.map(|_| ())
|
|
||||||
});
|
|
||||||
|
|
||||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||||
if let Err(e) = task.await {
|
if let Err(e) = task.await {
|
||||||
this.update(cx, |_this, cx| {
|
this.update(cx, |_this, cx| cx.emit(BackendEvent::error(e.to_string())))?;
|
||||||
cx.emit(BackendEvent::error(e.to_string()))
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}));
|
}));
|
||||||
@@ -171,25 +172,39 @@ impl Backend {
|
|||||||
|
|
||||||
/// Sign, broadcast and locally store an event. Emits
|
/// Sign, broadcast and locally store an event. Emits
|
||||||
/// [`BackendEvent::Published`] on success so stores can refresh.
|
/// [`BackendEvent::Published`] on success so stores can refresh.
|
||||||
pub fn send(&mut self, builder: EventBuilder, cx: &mut Context<Self>) {
|
///
|
||||||
let backend = self.inner.clone();
|
/// The returned receiver yields the outcome of this specific action,
|
||||||
|
/// so callers can show inline progress/errors instead of relying on
|
||||||
|
/// the global [`BackendEvent::Error`].
|
||||||
|
pub fn send(
|
||||||
|
&mut self,
|
||||||
|
builder: EventBuilder,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> flume::Receiver<Result<Event, Error>> {
|
||||||
|
let (tx, rx) = flume::bounded(1);
|
||||||
|
|
||||||
|
let backend = self.inner.clone();
|
||||||
let task = cx.background_spawn(async move { backend.send(builder).await });
|
let task = cx.background_spawn(async move { backend.send(builder).await });
|
||||||
|
|
||||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||||
match task.await {
|
let result = task.await;
|
||||||
|
|
||||||
|
match &result {
|
||||||
Ok(event) => {
|
Ok(event) => {
|
||||||
this.update(cx, |_this, cx| {
|
this.update(cx, |_this, cx| {
|
||||||
cx.emit(BackendEvent::Published(Box::new(event)));
|
cx.emit(BackendEvent::Published(Box::new(event.clone())));
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
this.update(cx, |_this, cx| {
|
this.update(cx, |_this, cx| cx.emit(BackendEvent::error(e.to_string())))?;
|
||||||
cx.emit(BackendEvent::error(e.to_string()))
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
|
tx.send_async(result)
|
||||||
|
.await
|
||||||
|
.map_err(|_| anyhow!("action result receiver dropped"))
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
rx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+106
-45
@@ -10,7 +10,6 @@ use crate::backend::{Backend, BackendEvent};
|
|||||||
pub struct RepoStore {
|
pub struct RepoStore {
|
||||||
addr: RepoAddr,
|
addr: RepoAddr,
|
||||||
addr_string: String,
|
addr_string: String,
|
||||||
|
|
||||||
pub announcement: Option<Announcement>,
|
pub announcement: Option<Announcement>,
|
||||||
/// `(refname, commit-id)` pairs from the latest state announcement.
|
/// `(refname, commit-id)` pairs from the latest state announcement.
|
||||||
pub refs: Vec<(String, String)>,
|
pub refs: Vec<(String, String)>,
|
||||||
@@ -20,9 +19,12 @@ pub struct RepoStore {
|
|||||||
pub patches: Vec<Event>,
|
pub patches: Vec<Event>,
|
||||||
pub pull_requests: Vec<Event>,
|
pub pull_requests: Vec<Event>,
|
||||||
statuses: Vec<Event>,
|
statuses: Vec<Event>,
|
||||||
|
/// Error of the last action initiated from this store, if any.
|
||||||
_subscription: Subscription,
|
pub last_error: Option<String>,
|
||||||
|
refreshing: bool,
|
||||||
|
refresh_dirty: bool,
|
||||||
tasks: Vec<Task<Result<(), Error>>>,
|
tasks: Vec<Task<Result<(), Error>>>,
|
||||||
|
_subscription: Subscription,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RepoStore {
|
impl RepoStore {
|
||||||
@@ -37,8 +39,7 @@ impl RepoStore {
|
|||||||
&& update.author == this.addr.owner)
|
&& update.author == this.addr.owner)
|
||||||
}
|
}
|
||||||
BackendEvent::Published(event) => {
|
BackendEvent::Published(event) => {
|
||||||
event.kind == Kind::GitRepoAnnouncement
|
event.kind == Kind::GitRepoAnnouncement && event.pubkey == this.addr.owner
|
||||||
&& event.pubkey == this.addr.owner
|
|
||||||
|| event.tags.iter().any(|t| {
|
|| event.tags.iter().any(|t| {
|
||||||
t.kind() == "a" && t.content() == Some(this.addr_string.as_str())
|
t.kind() == "a" && t.content() == Some(this.addr_string.as_str())
|
||||||
})
|
})
|
||||||
@@ -61,6 +62,9 @@ impl RepoStore {
|
|||||||
patches: Vec::new(),
|
patches: Vec::new(),
|
||||||
pull_requests: Vec::new(),
|
pull_requests: Vec::new(),
|
||||||
statuses: Vec::new(),
|
statuses: Vec::new(),
|
||||||
|
last_error: None,
|
||||||
|
refreshing: false,
|
||||||
|
refresh_dirty: false,
|
||||||
_subscription: subscription,
|
_subscription: subscription,
|
||||||
tasks: Vec::new(),
|
tasks: Vec::new(),
|
||||||
};
|
};
|
||||||
@@ -86,49 +90,92 @@ impl RepoStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Re-query the local database and update all fields.
|
/// Re-query the local database and update all fields.
|
||||||
|
///
|
||||||
|
/// Debounced: concurrent requests are coalesced into a single re-query
|
||||||
|
/// after the running one finishes.
|
||||||
pub fn refresh(&mut self, cx: &mut Context<Self>) {
|
pub fn refresh(&mut self, cx: &mut Context<Self>) {
|
||||||
|
if self.refreshing {
|
||||||
|
self.refresh_dirty = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.refreshing = true;
|
||||||
|
|
||||||
let client = Backend::global(cx).read(cx).client();
|
let client = Backend::global(cx).read(cx).client();
|
||||||
let addr = self.addr.clone();
|
let addr = self.addr.clone();
|
||||||
|
|
||||||
let task = cx.spawn(async move |this, cx| {
|
let task = cx.spawn(async move |this, cx| {
|
||||||
let db = client.database();
|
loop {
|
||||||
|
let queries = async {
|
||||||
|
let db = client.database();
|
||||||
|
|
||||||
let announcements = db.query(filters::announcement(&addr)).await?;
|
let announcements = db.query(filters::announcement(&addr)).await?;
|
||||||
let states = db.query(filters::state(&addr)).await?;
|
let states = db.query(filters::state(&addr)).await?;
|
||||||
let activity = db.query(filters::activity(&addr)).await?;
|
let activity = db.query(filters::activity(&addr)).await?;
|
||||||
|
|
||||||
this.update(cx, |this, cx| {
|
Ok::<_, Error>((announcements, states, activity))
|
||||||
this.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;
|
|
||||||
}
|
}
|
||||||
|
.await;
|
||||||
|
|
||||||
this.issues.clear();
|
let (announcements, states, activity) = match queries {
|
||||||
this.patches.clear();
|
Ok(results) => results,
|
||||||
this.pull_requests.clear();
|
Err(e) => {
|
||||||
this.statuses.clear();
|
return this.update(cx, |this, cx| {
|
||||||
|
this.refreshing = false;
|
||||||
for event in activity {
|
this.last_error = Some(e.to_string());
|
||||||
match event.kind {
|
cx.notify();
|
||||||
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),
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let again = this.update(cx, |this, cx| {
|
||||||
|
this.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.issues.clear();
|
||||||
|
this.patches.clear();
|
||||||
|
this.pull_requests.clear();
|
||||||
|
this.statuses.clear();
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
sort_newest_first(&mut this.issues);
|
|
||||||
sort_newest_first(&mut this.patches);
|
|
||||||
sort_newest_first(&mut this.pull_requests);
|
|
||||||
|
|
||||||
cx.notify();
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
@@ -171,12 +218,11 @@ impl RepoStore {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let builder = EventBuilder::new(Kind::GitPatch, patch)
|
let builder = EventBuilder::new(Kind::GitPatch, patch).tags([
|
||||||
.tags([
|
Tag::coordinate(self.addr.coordinate(), None),
|
||||||
Tag::coordinate(self.addr.coordinate(), None),
|
Tag::public_key(self.addr.owner),
|
||||||
Tag::public_key(self.addr.owner),
|
root_marker,
|
||||||
root_marker,
|
]);
|
||||||
]);
|
|
||||||
|
|
||||||
self.send(builder, cx);
|
self.send(builder, cx);
|
||||||
}
|
}
|
||||||
@@ -197,8 +243,23 @@ impl RepoStore {
|
|||||||
self.send(builder, cx);
|
self.send(builder, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send(&self, builder: EventBuilder, cx: &mut Context<Self>) {
|
fn send(&mut self, builder: EventBuilder, cx: &mut Context<Self>) {
|
||||||
Backend::global(cx).update(cx, |backend, cx| backend.send(builder, cx));
|
self.last_error = None;
|
||||||
|
|
||||||
|
let rx = Backend::global(cx).update(cx, |backend, cx| backend.send(builder, cx));
|
||||||
|
|
||||||
|
let task = cx.spawn(async move |this, cx| {
|
||||||
|
if let Ok(Err(e)) = rx.recv_async().await {
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.last_error = Some(e.to_string());
|
||||||
|
cx.notify();
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
|
self.tasks.push(task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,9 +11,10 @@ use crate::backend::{Backend, BackendEvent};
|
|||||||
pub struct RepoListStore {
|
pub struct RepoListStore {
|
||||||
pub announcements: Vec<Announcement>,
|
pub announcements: Vec<Announcement>,
|
||||||
author: Option<PublicKey>,
|
author: Option<PublicKey>,
|
||||||
|
refreshing: bool,
|
||||||
_subscription: Subscription,
|
refresh_dirty: bool,
|
||||||
tasks: Vec<Task<Result<(), Error>>>,
|
tasks: Vec<Task<Result<(), Error>>>,
|
||||||
|
_subscription: Subscription,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RepoListStore {
|
impl RepoListStore {
|
||||||
@@ -40,6 +41,8 @@ impl RepoListStore {
|
|||||||
let mut store = Self {
|
let mut store = Self {
|
||||||
announcements: Vec::new(),
|
announcements: Vec::new(),
|
||||||
author,
|
author,
|
||||||
|
refreshing: false,
|
||||||
|
refresh_dirty: false,
|
||||||
_subscription: subscription,
|
_subscription: subscription,
|
||||||
tasks: Vec::new(),
|
tasks: Vec::new(),
|
||||||
};
|
};
|
||||||
@@ -69,42 +72,72 @@ impl RepoListStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Re-query the local database. Latest announcement per repository wins.
|
/// 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.
|
||||||
pub fn refresh(&mut self, cx: &mut Context<Self>) {
|
pub fn refresh(&mut self, cx: &mut Context<Self>) {
|
||||||
|
if self.refreshing {
|
||||||
|
self.refresh_dirty = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.refreshing = true;
|
||||||
|
|
||||||
let client = Backend::global(cx).read(cx).client();
|
let client = Backend::global(cx).read(cx).client();
|
||||||
let author = self.author;
|
let author = self.author;
|
||||||
|
|
||||||
let task = cx.spawn(async move |this, cx| {
|
let task = cx.spawn(async move |this, cx| {
|
||||||
let filter = match author {
|
loop {
|
||||||
Some(a) => filters::announcements_by(a),
|
let filter = match author {
|
||||||
None => filters::all_announcements(500),
|
Some(a) => filters::announcements_by(a),
|
||||||
};
|
None => filters::all_announcements(500),
|
||||||
|
};
|
||||||
|
|
||||||
let events = client.database().query(filter).await?;
|
let events = match client.database().query(filter).await {
|
||||||
|
Ok(events) => events,
|
||||||
|
Err(_) => {
|
||||||
|
return this.update(cx, |this, _cx| {
|
||||||
|
this.refreshing = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
this.update(cx, |this, cx| {
|
let again = this.update(cx, |this, cx| {
|
||||||
let mut by_repo: HashMap<(String, String), Announcement> = HashMap::new();
|
let mut by_repo: HashMap<(String, String), Announcement> = HashMap::new();
|
||||||
|
|
||||||
for event in events {
|
for event in events {
|
||||||
let Some(announcement) = Announcement::from_event(&event) else {
|
let Some(announcement) = Announcement::from_event(&event) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let key = (announcement.owner.to_hex(), announcement.id.clone());
|
let key = (announcement.owner.to_hex(), announcement.id.clone());
|
||||||
|
|
||||||
match by_repo.get(&key) {
|
match by_repo.get(&key) {
|
||||||
Some(existing) if existing.created_at >= announcement.created_at => {}
|
Some(existing) if existing.created_at >= announcement.created_at => {}
|
||||||
_ => {
|
_ => {
|
||||||
by_repo.insert(key, announcement);
|
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;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
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();
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user