chore: improve message fetching
This commit is contained in:
@@ -12,9 +12,10 @@ use gpui::{
|
|||||||
};
|
};
|
||||||
use i18n::{shared_t, t};
|
use i18n::{shared_t, t};
|
||||||
use identity::Identity;
|
use identity::Identity;
|
||||||
|
use itertools::Itertools;
|
||||||
use nostr_connect::prelude::*;
|
use nostr_connect::prelude::*;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use registry::{Registry, RoomEmitter};
|
use registry::{Registry, RegistrySignal};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use settings::AppSettings;
|
use settings::AppSettings;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
@@ -194,7 +195,7 @@ impl ChatSpace {
|
|||||||
window,
|
window,
|
||||||
|this: &mut Self, _state, event, window, cx| {
|
|this: &mut Self, _state, event, window, cx| {
|
||||||
match event {
|
match event {
|
||||||
RoomEmitter::Open(room) => {
|
RegistrySignal::Open(room) => {
|
||||||
if let Some(room) = room.upgrade() {
|
if let Some(room) = room.upgrade() {
|
||||||
this.dock.update(cx, |this, cx| {
|
this.dock.update(cx, |this, cx| {
|
||||||
let panel = chat::init(room, window, cx);
|
let panel = chat::init(room, window, cx);
|
||||||
@@ -209,7 +210,7 @@ impl ChatSpace {
|
|||||||
window.push_notification(t!("common.room_error"), cx);
|
window.push_notification(t!("common.room_error"), cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RoomEmitter::Close(..) => {
|
RegistrySignal::Close(..) => {
|
||||||
this.dock.update(cx, |this, cx| {
|
this.dock.update(cx, |this, cx| {
|
||||||
this.focus_tab_panel(window, cx);
|
this.focus_tab_panel(window, cx);
|
||||||
|
|
||||||
@@ -415,6 +416,28 @@ impl ChatSpace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn all_panels(window: &mut Window, cx: &mut App) -> Option<Vec<u64>> {
|
||||||
|
let Some(Some(root)) = window.root::<Root>() else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(chatspace) = root.read(cx).view().clone().downcast::<ChatSpace>() else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let ids = chatspace
|
||||||
|
.read(cx)
|
||||||
|
.dock
|
||||||
|
.read(cx)
|
||||||
|
.items
|
||||||
|
.panel_ids(cx)
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|panel| panel.parse::<u64>().ok())
|
||||||
|
.collect_vec();
|
||||||
|
|
||||||
|
Some(ids)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for ChatSpace {
|
impl Render for ChatSpace {
|
||||||
|
|||||||
@@ -4,23 +4,27 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use anyhow::{anyhow, Error};
|
use anyhow::{anyhow, Error};
|
||||||
use assets::Assets;
|
use assets::Assets;
|
||||||
|
use common::event::EventUtils;
|
||||||
use global::constants::{
|
use global::constants::{
|
||||||
ALL_MESSAGES_SUB_ID, APP_ID, APP_NAME, BOOTSTRAP_RELAYS, METADATA_BATCH_LIMIT,
|
ALL_MESSAGES_ID, APP_ID, APP_NAME, BOOTSTRAP_RELAYS, METADATA_BATCH_LIMIT,
|
||||||
METADATA_BATCH_TIMEOUT, NEW_MESSAGE_SUB_ID, SEARCH_RELAYS,
|
METADATA_BATCH_TIMEOUT, NEW_MESSAGE_ID, SEARCH_RELAYS,
|
||||||
};
|
};
|
||||||
use global::{nostr_client, NostrSignal};
|
use global::{nostr_client, set_all_gift_wraps_fetched, NostrSignal};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, point, px, size, App, AppContext, Application, Bounds, KeyBinding, Menu, MenuItem,
|
actions, point, px, size, App, AppContext, Application, Bounds, KeyBinding, Menu, MenuItem,
|
||||||
SharedString, TitlebarOptions, WindowBackgroundAppearance, WindowBounds, WindowDecorations,
|
SharedString, TitlebarOptions, WindowBackgroundAppearance, WindowBounds, WindowDecorations,
|
||||||
WindowKind, WindowOptions,
|
WindowKind, WindowOptions,
|
||||||
};
|
};
|
||||||
use identity::Identity;
|
use identity::Identity;
|
||||||
|
use itertools::Itertools;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use registry::Registry;
|
use registry::Registry;
|
||||||
use smol::channel::{self, Sender};
|
use smol::channel::{self, Sender};
|
||||||
use theme::Theme;
|
use theme::Theme;
|
||||||
use ui::Root;
|
use ui::Root;
|
||||||
|
|
||||||
|
use crate::chatspace::ChatSpace;
|
||||||
|
|
||||||
pub(crate) mod chatspace;
|
pub(crate) mod chatspace;
|
||||||
pub(crate) mod views;
|
pub(crate) mod views;
|
||||||
|
|
||||||
@@ -130,7 +134,7 @@ fn main() {
|
|||||||
let duration = smol::Timer::after(Duration::from_secs(30));
|
let duration = smol::Timer::after(Duration::from_secs(30));
|
||||||
|
|
||||||
let recv = || async {
|
let recv = || async {
|
||||||
// prevent inline format
|
// no inline
|
||||||
(event_rx.recv().await).ok()
|
(event_rx.recv().await).ok()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -142,11 +146,10 @@ fn main() {
|
|||||||
match smol::future::or(recv(), timeout()).await {
|
match smol::future::or(recv(), timeout()).await {
|
||||||
Some(event) => {
|
Some(event) => {
|
||||||
// Process the gift wrap event unwrapping
|
// Process the gift wrap event unwrapping
|
||||||
let is_cached =
|
let cached = try_unwrap_event(&signal_tx, &mta_tx, &event, false).await;
|
||||||
try_unwrap_event(client, &signal_tx, &mta_tx, &event, false).await;
|
|
||||||
|
|
||||||
// Increment the total messages counter if message is not from cache
|
// Increment the total messages counter if message is not from cache
|
||||||
if !is_cached {
|
if !cached {
|
||||||
counter += 1;
|
counter += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +161,12 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
// Notify the UI that the processing is finished
|
||||||
signal_tx.send(NostrSignal::Finish).await.ok();
|
signal_tx.send(NostrSignal::Finish).await.ok();
|
||||||
|
// Mark all gift wraps as fetched
|
||||||
|
// For the next time Coop only needs to process new gift wraps
|
||||||
|
set_all_gift_wraps_fetched().await;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -239,7 +247,7 @@ fn main() {
|
|||||||
|
|
||||||
// Spawn a task to handle events from nostr channel
|
// Spawn a task to handle events from nostr channel
|
||||||
cx.spawn_in(window, async move |_, cx| {
|
cx.spawn_in(window, async move |_, cx| {
|
||||||
let all_messages_sub_id = SubscriptionId::new(ALL_MESSAGES_SUB_ID);
|
let all_messages = SubscriptionId::new(ALL_MESSAGES_ID);
|
||||||
|
|
||||||
while let Ok(signal) = signal_rx.recv().await {
|
while let Ok(signal) = signal_rx.recv().await {
|
||||||
cx.update(|window, cx| {
|
cx.update(|window, cx| {
|
||||||
@@ -252,18 +260,26 @@ fn main() {
|
|||||||
registry.update(cx, |this, cx| {
|
registry.update(cx, |this, cx| {
|
||||||
this.load_rooms(window, cx);
|
this.load_rooms(window, cx);
|
||||||
this.set_loading(false, cx);
|
this.set_loading(false, cx);
|
||||||
|
// Send a signal to refresh all opened rooms' messages
|
||||||
|
if let Some(ids) = ChatSpace::all_panels(window, cx) {
|
||||||
|
this.refresh_rooms(ids, cx);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Load chat rooms without setting as finished
|
// Load chat rooms without setting as finished
|
||||||
NostrSignal::PartialFinish => {
|
NostrSignal::PartialFinish => {
|
||||||
registry.update(cx, |this, cx| {
|
registry.update(cx, |this, cx| {
|
||||||
this.load_rooms(window, cx);
|
this.load_rooms(window, cx);
|
||||||
|
// Send a signal to refresh all opened rooms' messages
|
||||||
|
if let Some(ids) = ChatSpace::all_panels(window, cx) {
|
||||||
|
this.refresh_rooms(ids, cx);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Load chat rooms without setting as finished
|
// Load chat rooms without setting as finished
|
||||||
NostrSignal::Eose(subscription_id) => {
|
NostrSignal::Eose(subscription_id) => {
|
||||||
// Only load chat rooms if the subscription ID matches the all_messages_sub_id
|
// Only load chat rooms if the subscription ID matches the all_messages_sub_id
|
||||||
if subscription_id == all_messages_sub_id {
|
if subscription_id == all_messages {
|
||||||
registry.update(cx, |this, cx| {
|
registry.update(cx, |this, cx| {
|
||||||
this.load_rooms(window, cx);
|
this.load_rooms(window, cx);
|
||||||
});
|
});
|
||||||
@@ -354,7 +370,7 @@ async fn handle_nostr_notifications(
|
|||||||
mta_tx: &Sender<PublicKey>,
|
mta_tx: &Sender<PublicKey>,
|
||||||
event_tx: &Sender<Event>,
|
event_tx: &Sender<Event>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let new_messages_sub_id = SubscriptionId::new(NEW_MESSAGE_SUB_ID);
|
let new_messages = SubscriptionId::new(NEW_MESSAGE_ID);
|
||||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||||
|
|
||||||
let mut notifications = client.notifications();
|
let mut notifications = client.notifications();
|
||||||
@@ -379,9 +395,9 @@ async fn handle_nostr_notifications(
|
|||||||
|
|
||||||
match event.kind {
|
match event.kind {
|
||||||
Kind::GiftWrap => {
|
Kind::GiftWrap => {
|
||||||
if *subscription_id == new_messages_sub_id {
|
if *subscription_id == new_messages {
|
||||||
let event = event.as_ref();
|
let event = event.as_ref();
|
||||||
_ = try_unwrap_event(client, signal_tx, mta_tx, event, false).await;
|
_ = try_unwrap_event(signal_tx, mta_tx, event, false).await;
|
||||||
} else {
|
} else {
|
||||||
event_tx.send(event.into_owned()).await.ok();
|
event_tx.send(event.into_owned()).await.ok();
|
||||||
}
|
}
|
||||||
@@ -459,52 +475,56 @@ async fn sync_data_for_pubkeys(client: &Client, public_keys: BTreeSet<PublicKey>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Stores an unwrapped event in local database with reference to original
|
/// Stores an unwrapped event in local database with reference to original
|
||||||
async fn set_unwrapped(client: &Client, root: EventId, event: &Event) -> Result<(), Error> {
|
async fn set_unwrapped(root: EventId, unwrapped: &Event) -> Result<(), Error> {
|
||||||
// Must be use the random generated keys to sign this event
|
let client = nostr_client();
|
||||||
let event = EventBuilder::new(Kind::ApplicationSpecificData, event.as_json())
|
|
||||||
.tags(vec![Tag::identifier(root), Tag::event(root)])
|
// Save unwrapped event
|
||||||
|
client.database().save_event(unwrapped).await?;
|
||||||
|
|
||||||
|
// Create a reference event pointing to the unwrapped event
|
||||||
|
let event = EventBuilder::new(Kind::ApplicationSpecificData, "")
|
||||||
|
.tags(vec![Tag::identifier(root), Tag::event(unwrapped.id)])
|
||||||
.sign(&Keys::generate())
|
.sign(&Keys::generate())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Only save this event into the local database
|
// Save reference event
|
||||||
client.database().save_event(&event).await?;
|
client.database().save_event(&event).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves a previously unwrapped event from local database
|
/// Retrieves a previously unwrapped event from local database
|
||||||
async fn get_unwrapped(client: &Client, target: EventId) -> Result<Event, Error> {
|
async fn get_unwrapped(root: EventId) -> Result<Event, Error> {
|
||||||
|
let client = nostr_client();
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
.kind(Kind::ApplicationSpecificData)
|
.kind(Kind::ApplicationSpecificData)
|
||||||
.identifier(target)
|
.identifier(root)
|
||||||
.event(target)
|
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
if let Some(event) = client.database().query(filter).await?.first_owned() {
|
if let Some(event) = client.database().query(filter).await?.first_owned() {
|
||||||
Ok(Event::from_json(event.content)?)
|
let target_id = event.tags.event_ids().collect_vec()[0];
|
||||||
|
|
||||||
|
if let Some(event) = client.database().event_by_id(target_id).await? {
|
||||||
|
Ok(event)
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("Event is not cached yet"))
|
Err(anyhow!("Event not found."))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("Event is not cached yet."))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unwraps a gift-wrapped event and processes its contents.
|
/// Unwraps a gift-wrapped event and processes its contents.
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
/// * `event` - The gift-wrapped event to unwrap
|
|
||||||
/// * `incoming` - Whether this is a newly received event (true) or old
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
/// Returns `true` if the event was successfully loaded from cache or saved after unwrapping.
|
|
||||||
async fn try_unwrap_event(
|
async fn try_unwrap_event(
|
||||||
client: &Client,
|
|
||||||
signal_tx: &Sender<NostrSignal>,
|
signal_tx: &Sender<NostrSignal>,
|
||||||
mta_tx: &Sender<PublicKey>,
|
mta_tx: &Sender<PublicKey>,
|
||||||
event: &Event,
|
event: &Event,
|
||||||
incoming: bool,
|
incoming: bool,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
|
let client = nostr_client();
|
||||||
let mut is_cached = false;
|
let mut is_cached = false;
|
||||||
|
|
||||||
let event = match get_unwrapped(client, event.id).await {
|
let event = match get_unwrapped(event.id).await {
|
||||||
Ok(event) => {
|
Ok(event) => {
|
||||||
is_cached = true;
|
is_cached = true;
|
||||||
event
|
event
|
||||||
@@ -512,31 +532,32 @@ async fn try_unwrap_event(
|
|||||||
Err(_) => {
|
Err(_) => {
|
||||||
match client.unwrap_gift_wrap(event).await {
|
match client.unwrap_gift_wrap(event).await {
|
||||||
Ok(unwrap) => {
|
Ok(unwrap) => {
|
||||||
|
// Sign the unwrapped event with a RANDOM KEYS
|
||||||
let Ok(unwrapped) = unwrap.rumor.sign_with_keys(&Keys::generate()) else {
|
let Ok(unwrapped) = unwrap.rumor.sign_with_keys(&Keys::generate()) else {
|
||||||
|
log::error!("Failed to sign event");
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Save this event to the database for future use.
|
// Save this event to the database for future use.
|
||||||
if let Err(e) = set_unwrapped(client, event.id, &unwrapped).await {
|
if let Err(e) = set_unwrapped(event.id, &unwrapped).await {
|
||||||
log::error!("Failed to save event: {e}")
|
log::warn!("Failed to cache unwrapped event: {e}")
|
||||||
}
|
}
|
||||||
|
|
||||||
unwrapped
|
unwrapped
|
||||||
}
|
}
|
||||||
Err(_) => return false,
|
Err(e) => {
|
||||||
|
log::error!("Failed to unwrap event: {e}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Save the event to the database, use for query directly.
|
// Get all pubkeys from the event
|
||||||
if let Err(e) = client.database().save_event(&event).await {
|
let all_pubkeys = event.all_pubkeys();
|
||||||
log::error!("Failed to save event: {e}")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send all pubkeys to the batch to sync metadata
|
// Send all pubkeys to the metadata batch to sync data
|
||||||
mta_tx.send(event.pubkey).await.ok();
|
for public_key in all_pubkeys {
|
||||||
|
|
||||||
for public_key in event.tags.public_keys().copied() {
|
|
||||||
mta_tx.send(public_key).await.ok();
|
mta_tx.send(public_key).await.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ use identity::Identity;
|
|||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use registry::message::Message;
|
use registry::message::Message;
|
||||||
use registry::room::{Room, RoomKind, SendError};
|
use registry::room::{Room, RoomKind, RoomSignal, SendError};
|
||||||
use registry::Registry;
|
use registry::Registry;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use settings::AppSettings;
|
use settings::AppSettings;
|
||||||
@@ -111,14 +111,16 @@ impl Chat {
|
|||||||
subscriptions.push(cx.subscribe_in(
|
subscriptions.push(cx.subscribe_in(
|
||||||
&room,
|
&room,
|
||||||
window,
|
window,
|
||||||
move |this, _, incoming, _window, cx| {
|
move |this, _, signal, window, cx| {
|
||||||
|
match signal {
|
||||||
|
RoomSignal::NewMessage(event) => {
|
||||||
// Check if the incoming message is the same as the new message created by optimistic update
|
// Check if the incoming message is the same as the new message created by optimistic update
|
||||||
if this.prevent_duplicate_message(&incoming.0, cx) {
|
if this.prevent_duplicate_message(event, cx) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let old_len = this.messages.read(cx).len();
|
let old_len = this.messages.read(cx).len();
|
||||||
let message = incoming.0.clone().into_rc();
|
let message = event.clone().into_rc();
|
||||||
|
|
||||||
cx.update_entity(&this.messages, |this, cx| {
|
cx.update_entity(&this.messages, |this, cx| {
|
||||||
this.extend(vec![message]);
|
this.extend(vec![message]);
|
||||||
@@ -126,6 +128,11 @@ impl Chat {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.list_state.splice(old_len..old_len, 1);
|
this.list_state.splice(old_len..old_len, 1);
|
||||||
|
}
|
||||||
|
RoomSignal::Refresh => {
|
||||||
|
this.load_messages(window, cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
|
||||||
@@ -142,10 +149,10 @@ impl Chat {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
id: room.read(cx).id.to_string().into(),
|
||||||
image_cache: RetainAllImageCache::new(cx),
|
image_cache: RetainAllImageCache::new(cx),
|
||||||
focus_handle: cx.focus_handle(),
|
focus_handle: cx.focus_handle(),
|
||||||
uploading: false,
|
uploading: false,
|
||||||
id: room.read(cx).id.to_string().into(),
|
|
||||||
text_data: HashMap::new(),
|
text_data: HashMap::new(),
|
||||||
room,
|
room,
|
||||||
messages,
|
messages,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::{anyhow, Error};
|
use anyhow::{anyhow, Error};
|
||||||
use global::constants::{ALL_MESSAGES_SUB_ID, NEW_MESSAGE_SUB_ID, NIP17_RELAYS};
|
use global::constants::{NEW_MESSAGE_ID, NIP17_RELAYS};
|
||||||
use global::nostr_client;
|
use global::nostr_client;
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
@@ -10,6 +10,7 @@ use gpui::{
|
|||||||
TextAlign, UniformList, Window,
|
TextAlign, UniformList, Window,
|
||||||
};
|
};
|
||||||
use i18n::{shared_t, t};
|
use i18n::{shared_t, t};
|
||||||
|
use identity::Identity;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
@@ -207,28 +208,17 @@ impl MessagingRelays {
|
|||||||
_ = client.connect_relay(&relay).await;
|
_ = client.connect_relay(&relay).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
let all_msg_id = SubscriptionId::new(ALL_MESSAGES_SUB_ID);
|
let id = SubscriptionId::new(NEW_MESSAGE_ID);
|
||||||
let new_msg_id = SubscriptionId::new(NEW_MESSAGE_SUB_ID);
|
|
||||||
|
|
||||||
let all_messages = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
|
|
||||||
let new_messages = Filter::new()
|
let new_messages = Filter::new()
|
||||||
.kind(Kind::GiftWrap)
|
.kind(Kind::GiftWrap)
|
||||||
.pubkey(public_key)
|
.pubkey(public_key)
|
||||||
.limit(0);
|
.limit(0);
|
||||||
|
|
||||||
// Close old subscriptions
|
// Close old subscriptions
|
||||||
client.unsubscribe(&all_msg_id).await;
|
client.unsubscribe(&id).await;
|
||||||
client.unsubscribe(&new_msg_id).await;
|
|
||||||
|
|
||||||
// Subscribe to all messages
|
|
||||||
client
|
|
||||||
.subscribe_with_id(all_msg_id, all_messages, None)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Subscribe to new messages
|
// Subscribe to new messages
|
||||||
client
|
client.subscribe_with_id(id, new_messages, None).await?;
|
||||||
.subscribe_with_id(new_msg_id, new_messages, None)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
@@ -237,6 +227,10 @@ impl MessagingRelays {
|
|||||||
match task.await {
|
match task.await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
cx.update(|window, cx| {
|
cx.update(|window, cx| {
|
||||||
|
Identity::global(cx).update(cx, |this, cx| {
|
||||||
|
this.verify_dm_relays(window, cx);
|
||||||
|
});
|
||||||
|
// Close the current modal
|
||||||
window.close_modal(cx);
|
window.close_modal(cx);
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ use itertools::Itertools;
|
|||||||
use list_item::RoomListItem;
|
use list_item::RoomListItem;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use registry::room::{Room, RoomKind};
|
use registry::room::{Room, RoomKind};
|
||||||
use registry::{Registry, RoomEmitter};
|
use registry::{Registry, RegistrySignal};
|
||||||
use settings::AppSettings;
|
use settings::AppSettings;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
@@ -83,7 +83,7 @@ impl Sidebar {
|
|||||||
&chats,
|
&chats,
|
||||||
window,
|
window,
|
||||||
move |this, _chats, event, _window, cx| {
|
move |this, _chats, event, _window, cx| {
|
||||||
if let RoomEmitter::Request(kind) = event {
|
if let RegistrySignal::NewRequest(kind) = event {
|
||||||
this.indicator.update(cx, |this, cx| {
|
this.indicator.update(cx, |this, cx| {
|
||||||
*this = Some(kind.to_owned());
|
*this = Some(kind.to_owned());
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
|||||||
@@ -37,9 +37,11 @@ pub const NOSTR_CONNECT_RELAY: &str = "wss://relay.nsec.app";
|
|||||||
pub const NOSTR_CONNECT_TIMEOUT: u64 = 200;
|
pub const NOSTR_CONNECT_TIMEOUT: u64 = 200;
|
||||||
|
|
||||||
/// Unique ID for new message subscription.
|
/// Unique ID for new message subscription.
|
||||||
pub const NEW_MESSAGE_SUB_ID: &str = "listen_new_giftwraps";
|
pub const NEW_MESSAGE_ID: &str = "listen_new_giftwraps";
|
||||||
/// Unique ID for all messages subscription.
|
/// Unique ID for all messages subscription.
|
||||||
pub const ALL_MESSAGES_SUB_ID: &str = "listen_all_giftwraps";
|
pub const ALL_MESSAGES_ID: &str = "listen_all_giftwraps";
|
||||||
|
/// Unique ID for all newest messages subscription.
|
||||||
|
pub const ALL_NEWEST_MESSAGES_ID: &str = "listen_all_newest_giftwraps";
|
||||||
|
|
||||||
/// Total metadata requests will be grouped.
|
/// Total metadata requests will be grouped.
|
||||||
pub const METADATA_BATCH_LIMIT: usize = 100;
|
pub const METADATA_BATCH_LIMIT: usize = 100;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use std::fs;
|
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
@@ -67,7 +66,7 @@ pub fn first_run() -> &'static bool {
|
|||||||
let flag = support_dir().join(format!(".{}-first_run", env!("CARGO_PKG_VERSION")));
|
let flag = support_dir().join(format!(".{}-first_run", env!("CARGO_PKG_VERSION")));
|
||||||
|
|
||||||
if !flag.exists() {
|
if !flag.exists() {
|
||||||
if fs::write(&flag, "").is_err() {
|
if std::fs::write(&flag, "").is_err() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
true // First run
|
true // First run
|
||||||
@@ -76,3 +75,16 @@ pub fn first_run() -> &'static bool {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn set_all_gift_wraps_fetched() {
|
||||||
|
let flag = support_dir().join(".fetched");
|
||||||
|
|
||||||
|
if !flag.exists() && smol::fs::write(&flag, "").await.is_err() {
|
||||||
|
log::error!("Failed to create full run flag");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_gift_wraps_fetch_complete() -> bool {
|
||||||
|
let flag = support_dir().join(".fetched");
|
||||||
|
flag.exists()
|
||||||
|
}
|
||||||
|
|||||||
@@ -60,5 +60,5 @@ pub fn support_dir() -> &'static PathBuf {
|
|||||||
/// Returns the path to the `nostr` file.
|
/// Returns the path to the `nostr` file.
|
||||||
pub fn nostr_file() -> &'static PathBuf {
|
pub fn nostr_file() -> &'static PathBuf {
|
||||||
static NOSTR_FILE: OnceLock<PathBuf> = OnceLock::new();
|
static NOSTR_FILE: OnceLock<PathBuf> = OnceLock::new();
|
||||||
NOSTR_FILE.get_or_init(|| support_dir().join("nostr"))
|
NOSTR_FILE.get_or_init(|| support_dir().join("nostr-db"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ use anyhow::{anyhow, Error};
|
|||||||
use client_keys::ClientKeys;
|
use client_keys::ClientKeys;
|
||||||
use common::handle_auth::CoopAuthUrlHandler;
|
use common::handle_auth::CoopAuthUrlHandler;
|
||||||
use global::constants::{
|
use global::constants::{
|
||||||
ACCOUNT_D, ALL_MESSAGES_SUB_ID, NEW_MESSAGE_SUB_ID, NIP17_RELAYS, NIP65_RELAYS,
|
ACCOUNT_D, ALL_MESSAGES_ID, ALL_NEWEST_MESSAGES_ID, NEW_MESSAGE_ID, NIP17_RELAYS, NIP65_RELAYS,
|
||||||
NOSTR_CONNECT_TIMEOUT,
|
NOSTR_CONNECT_TIMEOUT,
|
||||||
};
|
};
|
||||||
use global::nostr_client;
|
use global::{is_gift_wraps_fetch_complete, nostr_client};
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, red, App, AppContext, Context, Entity, Global, ParentElement, SharedString, Styled,
|
div, red, App, AppContext, Context, Entity, Global, ParentElement, SharedString, Styled,
|
||||||
@@ -550,7 +550,7 @@ impl Identity {
|
|||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify_dm_relays(&self, window: &mut Window, cx: &mut Context<Self>) {
|
pub fn verify_dm_relays(&self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let Some(public_key) = self.public_key() else {
|
let Some(public_key) = self.public_key() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -632,22 +632,19 @@ impl Identity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn subscribe(client: &Client, public_key: PublicKey) -> Result<(), Error> {
|
pub(crate) async fn subscribe(client: &Client, public_key: PublicKey) -> Result<(), Error> {
|
||||||
let all_messages_sub_id = SubscriptionId::new(ALL_MESSAGES_SUB_ID);
|
let all_messages = SubscriptionId::new(ALL_MESSAGES_ID);
|
||||||
let new_messages_sub_id = SubscriptionId::new(NEW_MESSAGE_SUB_ID);
|
let all_newest_messages = SubscriptionId::new(ALL_NEWEST_MESSAGES_ID);
|
||||||
|
let new_messages = SubscriptionId::new(NEW_MESSAGE_ID);
|
||||||
|
// Subscription options
|
||||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||||
|
// Get the gift wraps fetch status
|
||||||
|
let is_completed = is_gift_wraps_fetch_complete();
|
||||||
|
|
||||||
client
|
client
|
||||||
.subscribe(
|
.subscribe(
|
||||||
Filter::new()
|
Filter::new()
|
||||||
.author(public_key)
|
.author(public_key)
|
||||||
.kinds(vec![
|
.kinds(vec![Kind::Metadata, Kind::ContactList, Kind::RelayList])
|
||||||
Kind::Metadata,
|
|
||||||
Kind::ContactList,
|
|
||||||
Kind::MuteList,
|
|
||||||
Kind::SimpleGroups,
|
|
||||||
Kind::InboxRelays,
|
|
||||||
Kind::RelayList,
|
|
||||||
])
|
|
||||||
.since(Timestamp::now()),
|
.since(Timestamp::now()),
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
@@ -665,15 +662,7 @@ impl Identity {
|
|||||||
|
|
||||||
client
|
client
|
||||||
.subscribe_with_id(
|
.subscribe_with_id(
|
||||||
all_messages_sub_id,
|
new_messages,
|
||||||
Filter::new().kind(Kind::GiftWrap).pubkey(public_key),
|
|
||||||
Some(opts),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
client
|
|
||||||
.subscribe_with_id(
|
|
||||||
new_messages_sub_id,
|
|
||||||
Filter::new()
|
Filter::new()
|
||||||
.kind(Kind::GiftWrap)
|
.kind(Kind::GiftWrap)
|
||||||
.pubkey(public_key)
|
.pubkey(public_key)
|
||||||
@@ -682,6 +671,26 @@ impl Identity {
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
if is_completed {
|
||||||
|
let week_ago: u64 = 7 * 24 * 60 * 60;
|
||||||
|
let since = Timestamp::from_secs(Timestamp::now().as_u64() - week_ago);
|
||||||
|
|
||||||
|
let filter = Filter::new()
|
||||||
|
.pubkey(public_key)
|
||||||
|
.kind(Kind::GiftWrap)
|
||||||
|
.since(since);
|
||||||
|
|
||||||
|
client
|
||||||
|
.subscribe_with_id(all_newest_messages, filter, Some(opts))
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
|
||||||
|
|
||||||
|
client
|
||||||
|
.subscribe_with_id(all_messages, filter, Some(opts))
|
||||||
|
.await?;
|
||||||
|
};
|
||||||
|
|
||||||
log::info!("Getting all user's metadata and messages...");
|
log::info!("Getting all user's metadata and messages...");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -31,10 +31,10 @@ struct GlobalRegistry(Entity<Registry>);
|
|||||||
impl Global for GlobalRegistry {}
|
impl Global for GlobalRegistry {}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum RoomEmitter {
|
pub enum RegistrySignal {
|
||||||
Open(WeakEntity<Room>),
|
Open(WeakEntity<Room>),
|
||||||
Close(u64),
|
Close(u64),
|
||||||
Request(RoomKind),
|
NewRequest(RoomKind),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Main registry for managing chat rooms and user profiles
|
/// Main registry for managing chat rooms and user profiles
|
||||||
@@ -55,7 +55,7 @@ pub struct Registry {
|
|||||||
subscriptions: SmallVec<[Subscription; 2]>,
|
subscriptions: SmallVec<[Subscription; 2]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<RoomEmitter> for Registry {}
|
impl EventEmitter<RegistrySignal> for Registry {}
|
||||||
|
|
||||||
impl Registry {
|
impl Registry {
|
||||||
/// Retrieve the Global Registry state
|
/// Retrieve the Global Registry state
|
||||||
@@ -85,8 +85,10 @@ impl Registry {
|
|||||||
|
|
||||||
// When any Room is created, load members metadata
|
// When any Room is created, load members metadata
|
||||||
subscriptions.push(cx.observe_new::<Room>(|this, _window, cx| {
|
subscriptions.push(cx.observe_new::<Room>(|this, _window, cx| {
|
||||||
|
let state = Self::global(cx);
|
||||||
let task = this.load_metadata(cx);
|
let task = this.load_metadata(cx);
|
||||||
Self::global(cx).update(cx, |this, cx| {
|
|
||||||
|
state.update(cx, |this, cx| {
|
||||||
this.set_persons_from_task(task, cx);
|
this.set_persons_from_task(task, cx);
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
@@ -207,7 +209,7 @@ impl Registry {
|
|||||||
/// Close a room.
|
/// Close a room.
|
||||||
pub fn close_room(&mut self, id: u64, cx: &mut Context<Self>) {
|
pub fn close_room(&mut self, id: u64, cx: &mut Context<Self>) {
|
||||||
if self.rooms.iter().any(|r| r.read(cx).id == id) {
|
if self.rooms.iter().any(|r| r.read(cx).id == id) {
|
||||||
cx.emit(RoomEmitter::Close(id));
|
cx.emit(RegistrySignal::Close(id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,7 +332,6 @@ impl Registry {
|
|||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// TODO: push notification
|
|
||||||
log::error!("Failed to load rooms: {e}")
|
log::error!("Failed to load rooms: {e}")
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -375,7 +376,18 @@ impl Registry {
|
|||||||
weak_room
|
weak_room
|
||||||
};
|
};
|
||||||
|
|
||||||
cx.emit(RoomEmitter::Open(weak_room));
|
cx.emit(RegistrySignal::Open(weak_room));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Refresh messages for a room in the global registry
|
||||||
|
pub fn refresh_rooms(&mut self, ids: Vec<u64>, cx: &mut Context<Self>) {
|
||||||
|
for room in self.rooms.iter() {
|
||||||
|
if ids.contains(&room.read(cx).id) {
|
||||||
|
room.update(cx, |this, cx| {
|
||||||
|
this.emit_refresh(cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a Nostr event into a Coop Message and push it to the belonging room
|
/// Parse a Nostr event into a Coop Message and push it to the belonging room
|
||||||
@@ -420,7 +432,7 @@ impl Registry {
|
|||||||
|
|
||||||
// Notify the UI about the new room
|
// Notify the UI about the new room
|
||||||
cx.defer_in(window, move |_this, _window, cx| {
|
cx.defer_in(window, move |_this, _window, cx| {
|
||||||
cx.emit(RoomEmitter::Request(RoomKind::default()));
|
cx.emit(RegistrySignal::NewRequest(RoomKind::default()));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,10 @@ pub(crate) const HOURS_IN_DAY: i64 = 24;
|
|||||||
pub(crate) const DAYS_IN_MONTH: i64 = 30;
|
pub(crate) const DAYS_IN_MONTH: i64 = 30;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Incoming(pub Message);
|
pub enum RoomSignal {
|
||||||
|
NewMessage(Message),
|
||||||
|
Refresh,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct SendError {
|
pub struct SendError {
|
||||||
@@ -69,7 +72,7 @@ impl PartialEq for Room {
|
|||||||
|
|
||||||
impl Eq for Room {}
|
impl Eq for Room {}
|
||||||
|
|
||||||
impl EventEmitter<Incoming> for Room {}
|
impl EventEmitter<RoomSignal> for Room {}
|
||||||
|
|
||||||
impl Room {
|
impl Room {
|
||||||
pub fn new(event: &Event) -> Self {
|
pub fn new(event: &Event) -> Self {
|
||||||
@@ -451,10 +454,16 @@ impl Room {
|
|||||||
.mentions(mentions)
|
.mentions(mentions)
|
||||||
.build()
|
.build()
|
||||||
{
|
{
|
||||||
cx.emit(Incoming(message));
|
cx.emit(RoomSignal::NewMessage(message));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Emits a signal to refresh the current room's messages.
|
||||||
|
pub fn emit_refresh(&mut self, cx: &mut Context<Self>) {
|
||||||
|
cx.emit(RoomSignal::Refresh);
|
||||||
|
log::info!("refresh room: {}", self.id);
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a temporary message for optimistic updates
|
/// Creates a temporary message for optimistic updates
|
||||||
///
|
///
|
||||||
/// This constructs an unsigned message with the current user as the author,
|
/// This constructs an unsigned message with the current user as the author,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use gpui::prelude::FluentBuilder;
|
|||||||
use gpui::{
|
use gpui::{
|
||||||
actions, canvas, div, px, AnyElement, AnyView, App, AppContext, Axis, Bounds, Context, Edges,
|
actions, canvas, div, px, AnyElement, AnyView, App, AppContext, Axis, Bounds, Context, Edges,
|
||||||
Entity, EntityId, EventEmitter, Focusable, InteractiveElement as _, IntoElement,
|
Entity, EntityId, EventEmitter, Focusable, InteractiveElement as _, IntoElement,
|
||||||
ParentElement as _, Pixels, Render, Styled, Subscription, WeakEntity, Window,
|
ParentElement as _, Pixels, Render, SharedString, Styled, Subscription, WeakEntity, Window,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::dock_area::dock::{Dock, DockPlacement};
|
use crate::dock_area::dock::{Dock, DockPlacement};
|
||||||
@@ -195,6 +195,25 @@ impl DockItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns all panel ids
|
||||||
|
pub fn panel_ids(&self, cx: &App) -> Vec<SharedString> {
|
||||||
|
match self {
|
||||||
|
Self::Tabs { view, .. } => view.read(cx).panel_ids(cx),
|
||||||
|
Self::Split { items, .. } => {
|
||||||
|
let mut total = vec![];
|
||||||
|
|
||||||
|
for item in items.iter() {
|
||||||
|
if let DockItem::Tabs { view, .. } = item {
|
||||||
|
total.extend(view.read(cx).panel_ids(cx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
total
|
||||||
|
}
|
||||||
|
Self::Panel { .. } => vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the views of the dock item.
|
/// Returns the views of the dock item.
|
||||||
pub fn view(&self) -> Arc<dyn PanelView> {
|
pub fn view(&self) -> Arc<dyn PanelView> {
|
||||||
match self {
|
match self {
|
||||||
@@ -252,6 +271,7 @@ impl DockItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the collapsed state of the dock area
|
||||||
pub fn set_collapsed(&self, collapsed: bool, window: &mut Window, cx: &mut App) {
|
pub fn set_collapsed(&self, collapsed: bool, window: &mut Window, cx: &mut App) {
|
||||||
match self {
|
match self {
|
||||||
DockItem::Tabs { view, .. } => {
|
DockItem::Tabs { view, .. } => {
|
||||||
|
|||||||
@@ -384,6 +384,11 @@ impl TabPanel {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return all panel ids
|
||||||
|
pub fn panel_ids<'a>(&'a self, cx: &'a App) -> Vec<SharedString> {
|
||||||
|
self.panels.iter().map(|panel| panel.panel_id(cx)).collect()
|
||||||
|
}
|
||||||
|
|
||||||
/// Return true if the tab panel is draggable.
|
/// Return true if the tab panel is draggable.
|
||||||
///
|
///
|
||||||
/// E.g. if the parent and self only have one panel, it is not draggable.
|
/// E.g. if the parent and self only have one panel, it is not draggable.
|
||||||
|
|||||||
Reference in New Issue
Block a user