chore: better handle async tasks (#142)

* improve some codes

* .
This commit is contained in:
reya
2025-09-07 08:33:21 +07:00
committed by GitHub
parent 60bca49200
commit e177facef4
6 changed files with 166 additions and 138 deletions

View File

@@ -40,7 +40,7 @@ use ui::dock_area::{ClosePanel, DockArea, DockItem};
use ui::modal::ModalButtonProps; use ui::modal::ModalButtonProps;
use ui::notification::Notification; use ui::notification::Notification;
use ui::popup_menu::PopupMenuExt; use ui::popup_menu::PopupMenuExt;
use ui::{h_flex, v_flex, ContextModal, IconName, Root, Sizable, StyledExt}; use ui::{h_flex, v_flex, ContextModal, Disableable, IconName, Root, Sizable, StyledExt};
use crate::actions::{DarkMode, Logout, Settings}; use crate::actions::{DarkMode, Logout, Settings};
use crate::views::compose::compose_button; use crate::views::compose::compose_button;
@@ -64,10 +64,17 @@ pub fn new_account(window: &mut Window, cx: &mut App) {
} }
pub struct ChatSpace { pub struct ChatSpace {
// Workspace
title_bar: Entity<TitleBar>, title_bar: Entity<TitleBar>,
dock: Entity<DockArea>, dock: Entity<DockArea>,
auth_requests: Vec<(String, RelayUrl)>,
// Temporarily store all authentication requests
auth_requests: HashMap<AuthRequest, bool>,
// Local state to determine if the user has set up NIP-17 relays
has_nip17_relays: bool, has_nip17_relays: bool,
// System
_subscriptions: SmallVec<[Subscription; 3]>, _subscriptions: SmallVec<[Subscription; 3]>,
_tasks: SmallVec<[Task<()>; 5]>, _tasks: SmallVec<[Task<()>; 5]>,
} }
@@ -174,7 +181,7 @@ impl ChatSpace {
Self { Self {
dock, dock,
title_bar, title_bar,
auth_requests: vec![], auth_requests: HashMap::new(),
has_nip17_relays: true, has_nip17_relays: true,
_subscriptions: subscriptions, _subscriptions: subscriptions,
_tasks: tasks, _tasks: tasks,
@@ -279,9 +286,10 @@ impl ChatSpace {
loop { loop {
if client.has_signer().await { if client.has_signer().await {
total_loops += 1;
if css.gift_wrap_processing.load(Ordering::Acquire) { if css.gift_wrap_processing.load(Ordering::Acquire) {
is_start_processing = true; is_start_processing = true;
total_loops += 1;
// Reset gift wrap processing flag // Reset gift wrap processing flag
let _ = css.gift_wrap_processing.compare_exchange( let _ = css.gift_wrap_processing.compare_exchange(
@@ -295,8 +303,8 @@ impl ChatSpace {
ingester.send(signal).await; ingester.send(signal).await;
} else { } else {
// Only run further if we are already processing // Only run further if we are already processing
// Wait until after 3 loops to prevent exiting early while events are still being processed // Wait until after 2 loops to prevent exiting early while events are still being processed
if is_start_processing && total_loops >= 3 { if is_start_processing && total_loops >= 2 {
let signal = Signal::GiftWrapProcess(UnwrappingStatus::Complete); let signal = Signal::GiftWrapProcess(UnwrappingStatus::Complete);
ingester.send(signal).await; ingester.send(signal).await;
@@ -522,21 +530,19 @@ impl ChatSpace {
}); });
} }
Signal::Auth(req) => { Signal::Auth(req) => {
let relay_url = &req.url; let url = &req.url;
let challenge = &req.challenge;
let auto_auth = AppSettings::get_auto_auth(cx); let auto_auth = AppSettings::get_auto_auth(cx);
let is_authenticated_relays = let is_authenticated = AppSettings::read_global(cx).is_authenticated(url);
AppSettings::read_global(cx).is_authenticated_relays(relay_url);
view.update(cx, |this, cx| { view.update(cx, |this, cx| {
this.push_auth_request(challenge, relay_url, cx); this.push_auth_request(&req, cx);
if auto_auth && is_authenticated_relays { if auto_auth && is_authenticated {
// Automatically authenticate if the relay is authenticated before // Automatically authenticate if the relay is authenticated before
this.auth(challenge, relay_url, window, cx); this.auth(req, window, cx);
} else { } else {
// Otherwise open the auth request popup // Otherwise open the auth request popup
this.open_auth_request(challenge, relay_url, window, cx); this.open_auth_request(req, window, cx);
} }
}) })
.ok(); .ok();
@@ -780,19 +786,18 @@ impl ChatSpace {
}; };
} }
fn auth( fn auth(&mut self, req: AuthRequest, window: &mut Window, cx: &mut Context<Self>) {
&mut self,
challenge: &str,
url: &RelayUrl,
window: &mut Window,
cx: &mut Context<Self>,
) {
let settings = AppSettings::global(cx); let settings = AppSettings::global(cx);
let challenge = challenge.to_string();
let url = url.to_owned(); let challenge = req.challenge.to_owned();
let url = req.url.to_owned();
let challenge_clone = challenge.clone(); let challenge_clone = challenge.clone();
let url_clone = url.clone(); let url_clone = url.clone();
// Set Coop is sending auth for this request
self.sending_auth_request(&challenge, cx);
let task: Task<Result<(), Error>> = cx.background_spawn(async move { let task: Task<Result<(), Error>> = cx.background_spawn(async move {
let client = nostr_client(); let client = nostr_client();
let css = css(); let css = css();
@@ -885,7 +890,7 @@ impl ChatSpace {
.ok(); .ok();
} }
Err(e) => { Err(e) => {
cx.update(|window, cx| { this.update_in(cx, |_, window, cx| {
window.push_notification(Notification::error(e.to_string()), cx); window.push_notification(Notification::error(e.to_string()), cx);
}) })
.ok(); .ok();
@@ -895,16 +900,10 @@ impl ChatSpace {
.detach(); .detach();
} }
fn open_auth_request( fn open_auth_request(&mut self, req: AuthRequest, window: &mut Window, cx: &mut Context<Self>) {
&mut self,
challenge: &str,
relay_url: &RelayUrl,
window: &mut Window,
cx: &mut Context<Self>,
) {
let weak_view = cx.entity().downgrade(); let weak_view = cx.entity().downgrade();
let challenge = challenge.to_string(); let challenge = req.challenge.to_owned();
let relay_url = relay_url.to_owned(); let relay_url = req.url.to_owned();
let url_as_string = SharedString::from(relay_url.to_string()); let url_as_string = SharedString::from(relay_url.to_string());
let note = Notification::new() let note = Notification::new()
@@ -929,19 +928,25 @@ impl ChatSpace {
) )
.into_any_element() .into_any_element()
}) })
.action(move |_window, _cx| { .action(move |_window, cx| {
let weak_view = weak_view.clone(); let weak_view = weak_view.clone();
let challenge = challenge.clone(); let req = req.clone();
let relay_url = relay_url.clone(); let loading = weak_view
.read_with(cx, |this, cx| {
this.is_sending_auth_request(&req.challenge, cx)
})
.unwrap_or_default();
Button::new("approve") Button::new("approve")
.label(t!("common.approve")) .label(t!("common.approve"))
.small() .small()
.primary() .primary()
.loading(loading)
.disabled(loading)
.on_click(move |_e, window, cx| { .on_click(move |_e, window, cx| {
weak_view weak_view
.update(cx, |this, cx| { .update(cx, |this, cx| {
this.auth(&challenge, &relay_url, window, cx); this.auth(req.clone(), window, cx);
}) })
.ok(); .ok();
}) })
@@ -951,23 +956,42 @@ impl ChatSpace {
} }
fn reopen_auth_request(&mut self, window: &mut Window, cx: &mut Context<Self>) { fn reopen_auth_request(&mut self, window: &mut Window, cx: &mut Context<Self>) {
for (challenge, relay_url) in self.auth_requests.clone().iter() { for req in self.auth_requests.clone().into_iter() {
self.open_auth_request(challenge, relay_url, window, cx); self.open_auth_request(req.0, window, cx);
} }
} }
fn push_auth_request(&mut self, challenge: &str, url: &RelayUrl, cx: &mut Context<Self>) { fn push_auth_request(&mut self, req: &AuthRequest, cx: &mut Context<Self>) {
self.auth_requests.push((challenge.into(), url.to_owned())); self.auth_requests.insert(req.to_owned(), false);
cx.notify(); cx.notify();
} }
fn remove_auth_request(&mut self, challenge: &str, cx: &mut Context<Self>) { fn sending_auth_request(&mut self, challenge: &str, cx: &mut Context<Self>) {
if let Some(ix) = self.auth_requests.iter().position(|(c, _)| c == challenge) { for (req, status) in self.auth_requests.iter_mut() {
self.auth_requests.remove(ix); if req.challenge == challenge {
cx.notify(); *status = true;
cx.notify();
}
} }
} }
fn is_sending_auth_request(&self, challenge: &str, _cx: &App) -> bool {
if let Some(req) = self
.auth_requests
.iter()
.find(|(req, _)| req.challenge == challenge)
{
req.1.to_owned()
} else {
false
}
}
fn remove_auth_request(&mut self, challenge: &str, cx: &mut Context<Self>) {
self.auth_requests.retain(|r, _| r.challenge != challenge);
cx.notify();
}
fn set_onboarding_layout(&mut self, window: &mut Window, cx: &mut Context<Self>) { fn set_onboarding_layout(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let panel = Arc::new(onboarding::init(window, cx)); let panel = Arc::new(onboarding::init(window, cx));
let center = DockItem::panel(panel); let center = DockItem::panel(panel);
@@ -1016,6 +1040,11 @@ impl ChatSpace {
}); });
} }
fn set_no_nip17_relays(&mut self, cx: &mut Context<Self>) {
self.has_nip17_relays = false;
cx.notify();
}
fn load_local_account(&mut self, window: &mut Window, cx: &mut Context<Self>) { fn load_local_account(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let task = cx.background_spawn(async move { let task = cx.background_spawn(async move {
let client = nostr_client(); let client = nostr_client();
@@ -1059,11 +1088,6 @@ impl ChatSpace {
.detach(); .detach();
} }
fn set_no_nip17_relays(&mut self, cx: &mut Context<Self>) {
self.has_nip17_relays = false;
cx.notify();
}
fn on_settings(&mut self, _ev: &Settings, window: &mut Window, cx: &mut Context<Self>) { fn on_settings(&mut self, _ev: &Settings, window: &mut Window, cx: &mut Context<Self>) {
let view = preferences::init(window, cx); let view = preferences::init(window, cx);

View File

@@ -15,6 +15,7 @@ use gpui::{
use i18n::{shared_t, t}; use i18n::{shared_t, t};
use nostr_connect::prelude::*; use nostr_connect::prelude::*;
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use smallvec::{smallvec, SmallVec};
use theme::ActiveTheme; use theme::ActiveTheme;
use ui::avatar::Avatar; use ui::avatar::Avatar;
use ui::button::{Button, ButtonVariants}; use ui::button::{Button, ButtonVariants};
@@ -44,6 +45,7 @@ pub struct Account {
// Panel // Panel
name: SharedString, name: SharedString,
focus_handle: FocusHandle, focus_handle: FocusHandle,
_tasks: SmallVec<[Task<()>; 1]>,
} }
impl Account { impl Account {
@@ -59,6 +61,7 @@ impl Account {
loading: false, loading: false,
name: "Account".into(), name: "Account".into(),
focus_handle: cx.focus_handle(), focus_handle: cx.focus_handle(),
_tasks: smallvec![],
}) })
} }
@@ -89,24 +92,25 @@ impl Account {
// Handle auth url with the default browser // Handle auth url with the default browser
signer.auth_url_handler(CoopAuthUrlHandler); signer.auth_url_handler(CoopAuthUrlHandler);
// Handle connection self._tasks.push(
cx.spawn_in(window, async move |_this, cx| { // Handle connection
let client = nostr_client(); cx.spawn_in(window, async move |_this, cx| {
let client = nostr_client();
match signer.bunker_uri().await { match signer.bunker_uri().await {
Ok(_) => { Ok(_) => {
// Set the client's signer with the current nostr connect instance // Set the client's signer with the current nostr connect instance
client.set_signer(signer).await; client.set_signer(signer).await;
}
Err(e) => {
cx.update(|window, cx| {
window.push_notification(e.to_string(), cx);
})
.ok();
}
} }
Err(e) => { }),
cx.update(|window, cx| { );
window.push_notification(e.to_string(), cx);
})
.ok();
}
}
})
.detach();
} }
fn set_proxy(&mut self, window: &mut Window, cx: &mut Context<Self>) { fn set_proxy(&mut self, window: &mut Window, cx: &mut Context<Self>) {
@@ -240,24 +244,26 @@ impl Account {
} }
fn logout(&mut self, _window: &mut Window, cx: &mut Context<Self>) { fn logout(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
cx.background_spawn(async move { self._tasks.push(
let client = nostr_client(); // Reset the nostr client in the background
let ingester = ingester(); cx.background_spawn(async move {
let client = nostr_client();
let ingester = ingester();
let filter = Filter::new() let filter = Filter::new()
.kind(Kind::ApplicationSpecificData) .kind(Kind::ApplicationSpecificData)
.identifier(ACCOUNT_IDENTIFIER); .identifier(ACCOUNT_IDENTIFIER);
// Delete account // Delete account
client.database().delete(filter).await.ok(); client.database().delete(filter).await.ok();
// Unset the client's signer // Unset the client's signer
client.unset_signer().await; client.unset_signer().await;
// Notify the channel about the signer being unset // Notify the channel about the signer being unset
ingester.send(Signal::SignerUnset).await; ingester.send(Signal::SignerUnset).await;
}) }),
.detach(); );
} }
fn set_loading(&mut self, status: bool, cx: &mut Context<Self>) { fn set_loading(&mut self, status: bool, cx: &mut Context<Self>) {

View File

@@ -168,26 +168,28 @@ impl Chat {
} }
/// Load all messages belonging to this room /// Load all messages belonging to this room
fn load_messages(&self, window: &mut Window, cx: &mut Context<Self>) { fn load_messages(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let load_messages = self.room.read(cx).load_messages(cx); let load_messages = self.room.read(cx).load_messages(cx);
cx.spawn_in(window, async move |this, cx| { self._tasks.push(
match load_messages.await { // Run the task in the background
Ok(events) => { cx.spawn_in(window, async move |this, cx| {
this.update(cx, |this, cx| { match load_messages.await {
this.insert_messages(events, cx); Ok(events) => {
}) this.update(cx, |this, cx| {
.ok(); this.insert_messages(events, cx);
} })
Err(e) => { .ok();
cx.update(|window, cx| { }
window.push_notification(e.to_string(), cx); Err(e) => {
}) cx.update(|window, cx| {
.ok(); window.push_notification(e.to_string(), cx);
} })
}; .ok();
}) }
.detach(); };
}),
);
} }
#[allow(dead_code)] #[allow(dead_code)]

View File

@@ -9,7 +9,7 @@ use gpui::prelude::FluentBuilder;
use gpui::{ use gpui::{
div, img, px, relative, svg, AnyElement, App, AppContext, ClipboardItem, Context, Entity, div, img, px, relative, svg, AnyElement, App, AppContext, ClipboardItem, Context, Entity,
EventEmitter, FocusHandle, Focusable, Image, InteractiveElement, IntoElement, ParentElement, EventEmitter, FocusHandle, Focusable, Image, InteractiveElement, IntoElement, ParentElement,
Render, SharedString, StatefulInteractiveElement, Styled, Subscription, Window, Render, SharedString, StatefulInteractiveElement, Styled, Subscription, Task, Window,
}; };
use i18n::{shared_t, t}; use i18n::{shared_t, t};
use nostr_connect::prelude::*; use nostr_connect::prelude::*;
@@ -66,8 +66,8 @@ pub struct Onboarding {
// Panel // Panel
name: SharedString, name: SharedString,
focus_handle: FocusHandle, focus_handle: FocusHandle,
#[allow(dead_code)] _subscriptions: SmallVec<[Subscription; 2]>,
subscriptions: SmallVec<[Subscription; 2]>, _tasks: SmallVec<[Task<()>; 1]>,
} }
impl Onboarding { impl Onboarding {
@@ -104,10 +104,11 @@ impl Onboarding {
nostr_connect, nostr_connect,
nostr_connect_uri, nostr_connect_uri,
qr_code, qr_code,
subscriptions,
connecting: false, connecting: false,
name: "Onboarding".into(), name: "Onboarding".into(),
focus_handle: cx.focus_handle(), focus_handle: cx.focus_handle(),
_subscriptions: subscriptions,
_tasks: smallvec![],
} }
} }
@@ -131,36 +132,37 @@ impl Onboarding {
cx.notify(); cx.notify();
}); });
cx.spawn_in(window, async move |this, cx| { self._tasks.push(
let client = nostr_client(); // Wait for Nostr Connect approval
let connect = this.read_with(cx, |this, cx| this.nostr_connect.read(cx).clone()); cx.spawn_in(window, async move |this, cx| {
let client = nostr_client();
let connect = this.read_with(cx, |this, cx| this.nostr_connect.read(cx).clone());
if let Ok(Some(signer)) = connect { if let Ok(Some(signer)) = connect {
match signer.bunker_uri().await { match signer.bunker_uri().await {
Ok(uri) => { Ok(uri) => {
this.update(cx, |this, cx| { this.update(cx, |this, cx| {
this.set_connecting(cx); this.set_connecting(cx);
this.write_uri_to_disk(&uri, cx); this.write_uri_to_disk(&uri, cx);
}) })
.ok(); .ok();
// Set the client's signer with the current nostr connect instance // Set the client's signer with the current nostr connect instance
client.set_signer(signer).await; client.set_signer(signer).await;
} }
Err(e) => { Err(e) => {
log::warn!("Nostr Connect instance (QR Code) is timeout. TODO: fix this"); this.update_in(cx, |_, window, cx| {
this.update_in(cx, |_, window, cx| { window.push_notification(
window.push_notification( Notification::error(e.to_string()).title("Nostr Connect"),
Notification::error(e.to_string()).title("Nostr Connect"), cx,
cx, );
); })
}) .ok();
.ok(); }
} };
}; }
} }),
}) )
.detach();
} }
fn set_proxy(&mut self, window: &mut Window, cx: &mut Context<Self>) { fn set_proxy(&mut self, window: &mut Window, cx: &mut Context<Self>) {

View File

@@ -11,9 +11,7 @@ use registry::room::RoomKind;
use registry::Registry; use registry::Registry;
use settings::AppSettings; use settings::AppSettings;
use theme::ActiveTheme; use theme::ActiveTheme;
use ui::actions::OpenProfile;
use ui::avatar::Avatar; use ui::avatar::Avatar;
use ui::context_menu::ContextMenuExt;
use ui::modal::ModalButtonProps; use ui::modal::ModalButtonProps;
use ui::skeleton::Skeleton; use ui::skeleton::Skeleton;
use ui::{h_flex, ContextModal, StyledExt}; use ui::{h_flex, ContextModal, StyledExt};
@@ -167,10 +165,6 @@ impl RenderOnce for RoomListItem {
.child(created_at), .child(created_at),
), ),
) )
.context_menu(move |this, _window, _cx| {
// TODO: add share chat room
this.menu(t!("profile.view"), Box::new(OpenProfile(public_key)))
})
.hover(|this| this.bg(cx.theme().elevated_surface_background)) .hover(|this| this.bg(cx.theme().elevated_surface_background))
.on_click(move |event, window, cx| { .on_click(move |event, window, cx| {
handler(event, window, cx); handler(event, window, cx);

View File

@@ -176,12 +176,12 @@ impl AppSettings {
!self.setting_values.authenticated_relays.is_empty() && self.setting_values.auto_auth !self.setting_values.authenticated_relays.is_empty() && self.setting_values.auto_auth
} }
pub fn is_authenticated_relays(&self, url: &RelayUrl) -> bool { pub fn is_authenticated(&self, url: &RelayUrl) -> bool {
self.setting_values.authenticated_relays.contains(url) self.setting_values.authenticated_relays.contains(url)
} }
pub fn push_relay(&mut self, relay_url: &RelayUrl, cx: &mut Context<Self>) { pub fn push_relay(&mut self, relay_url: &RelayUrl, cx: &mut Context<Self>) {
if !self.is_authenticated_relays(relay_url) { if !self.is_authenticated(relay_url) {
self.setting_values self.setting_values
.authenticated_relays .authenticated_relays
.push(relay_url.to_owned()); .push(relay_url.to_owned());