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::notification::Notification;
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::views::compose::compose_button;
@@ -64,10 +64,17 @@ pub fn new_account(window: &mut Window, cx: &mut App) {
}
pub struct ChatSpace {
// Workspace
title_bar: Entity<TitleBar>,
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,
// System
_subscriptions: SmallVec<[Subscription; 3]>,
_tasks: SmallVec<[Task<()>; 5]>,
}
@@ -174,7 +181,7 @@ impl ChatSpace {
Self {
dock,
title_bar,
auth_requests: vec![],
auth_requests: HashMap::new(),
has_nip17_relays: true,
_subscriptions: subscriptions,
_tasks: tasks,
@@ -279,9 +286,10 @@ impl ChatSpace {
loop {
if client.has_signer().await {
total_loops += 1;
if css.gift_wrap_processing.load(Ordering::Acquire) {
is_start_processing = true;
total_loops += 1;
// Reset gift wrap processing flag
let _ = css.gift_wrap_processing.compare_exchange(
@@ -295,8 +303,8 @@ impl ChatSpace {
ingester.send(signal).await;
} else {
// Only run further if we are already processing
// Wait until after 3 loops to prevent exiting early while events are still being processed
if is_start_processing && total_loops >= 3 {
// Wait until after 2 loops to prevent exiting early while events are still being processed
if is_start_processing && total_loops >= 2 {
let signal = Signal::GiftWrapProcess(UnwrappingStatus::Complete);
ingester.send(signal).await;
@@ -522,21 +530,19 @@ impl ChatSpace {
});
}
Signal::Auth(req) => {
let relay_url = &req.url;
let challenge = &req.challenge;
let url = &req.url;
let auto_auth = AppSettings::get_auto_auth(cx);
let is_authenticated_relays =
AppSettings::read_global(cx).is_authenticated_relays(relay_url);
let is_authenticated = AppSettings::read_global(cx).is_authenticated(url);
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
this.auth(challenge, relay_url, window, cx);
this.auth(req, window, cx);
} else {
// Otherwise open the auth request popup
this.open_auth_request(challenge, relay_url, window, cx);
this.open_auth_request(req, window, cx);
}
})
.ok();
@@ -780,19 +786,18 @@ impl ChatSpace {
};
}
fn auth(
&mut self,
challenge: &str,
url: &RelayUrl,
window: &mut Window,
cx: &mut Context<Self>,
) {
fn auth(&mut self, req: AuthRequest, window: &mut Window, cx: &mut Context<Self>) {
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 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 client = nostr_client();
let css = css();
@@ -885,7 +890,7 @@ impl ChatSpace {
.ok();
}
Err(e) => {
cx.update(|window, cx| {
this.update_in(cx, |_, window, cx| {
window.push_notification(Notification::error(e.to_string()), cx);
})
.ok();
@@ -895,16 +900,10 @@ impl ChatSpace {
.detach();
}
fn open_auth_request(
&mut self,
challenge: &str,
relay_url: &RelayUrl,
window: &mut Window,
cx: &mut Context<Self>,
) {
fn open_auth_request(&mut self, req: AuthRequest, window: &mut Window, cx: &mut Context<Self>) {
let weak_view = cx.entity().downgrade();
let challenge = challenge.to_string();
let relay_url = relay_url.to_owned();
let challenge = req.challenge.to_owned();
let relay_url = req.url.to_owned();
let url_as_string = SharedString::from(relay_url.to_string());
let note = Notification::new()
@@ -929,19 +928,25 @@ impl ChatSpace {
)
.into_any_element()
})
.action(move |_window, _cx| {
.action(move |_window, cx| {
let weak_view = weak_view.clone();
let challenge = challenge.clone();
let relay_url = relay_url.clone();
let req = req.clone();
let loading = weak_view
.read_with(cx, |this, cx| {
this.is_sending_auth_request(&req.challenge, cx)
})
.unwrap_or_default();
Button::new("approve")
.label(t!("common.approve"))
.small()
.primary()
.loading(loading)
.disabled(loading)
.on_click(move |_e, window, cx| {
weak_view
.update(cx, |this, cx| {
this.auth(&challenge, &relay_url, window, cx);
this.auth(req.clone(), window, cx);
})
.ok();
})
@@ -951,23 +956,42 @@ impl ChatSpace {
}
fn reopen_auth_request(&mut self, window: &mut Window, cx: &mut Context<Self>) {
for (challenge, relay_url) in self.auth_requests.clone().iter() {
self.open_auth_request(challenge, relay_url, window, cx);
for req in self.auth_requests.clone().into_iter() {
self.open_auth_request(req.0, window, cx);
}
}
fn push_auth_request(&mut self, challenge: &str, url: &RelayUrl, cx: &mut Context<Self>) {
self.auth_requests.push((challenge.into(), url.to_owned()));
fn push_auth_request(&mut self, req: &AuthRequest, cx: &mut Context<Self>) {
self.auth_requests.insert(req.to_owned(), false);
cx.notify();
}
fn remove_auth_request(&mut self, challenge: &str, cx: &mut Context<Self>) {
if let Some(ix) = self.auth_requests.iter().position(|(c, _)| c == challenge) {
self.auth_requests.remove(ix);
cx.notify();
fn sending_auth_request(&mut self, challenge: &str, cx: &mut Context<Self>) {
for (req, status) in self.auth_requests.iter_mut() {
if req.challenge == challenge {
*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>) {
let panel = Arc::new(onboarding::init(window, cx));
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>) {
let task = cx.background_spawn(async move {
let client = nostr_client();
@@ -1059,11 +1088,6 @@ impl ChatSpace {
.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>) {
let view = preferences::init(window, cx);

View File

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

View File

@@ -9,7 +9,7 @@ use gpui::prelude::FluentBuilder;
use gpui::{
div, img, px, relative, svg, AnyElement, App, AppContext, ClipboardItem, Context, Entity,
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 nostr_connect::prelude::*;
@@ -66,8 +66,8 @@ pub struct Onboarding {
// Panel
name: SharedString,
focus_handle: FocusHandle,
#[allow(dead_code)]
subscriptions: SmallVec<[Subscription; 2]>,
_subscriptions: SmallVec<[Subscription; 2]>,
_tasks: SmallVec<[Task<()>; 1]>,
}
impl Onboarding {
@@ -104,10 +104,11 @@ impl Onboarding {
nostr_connect,
nostr_connect_uri,
qr_code,
subscriptions,
connecting: false,
name: "Onboarding".into(),
focus_handle: cx.focus_handle(),
_subscriptions: subscriptions,
_tasks: smallvec![],
}
}
@@ -131,36 +132,37 @@ impl Onboarding {
cx.notify();
});
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());
self._tasks.push(
// Wait for Nostr Connect approval
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 {
match signer.bunker_uri().await {
Ok(uri) => {
this.update(cx, |this, cx| {
this.set_connecting(cx);
this.write_uri_to_disk(&uri, cx);
})
.ok();
if let Ok(Some(signer)) = connect {
match signer.bunker_uri().await {
Ok(uri) => {
this.update(cx, |this, cx| {
this.set_connecting(cx);
this.write_uri_to_disk(&uri, cx);
})
.ok();
// Set the client's signer with the current nostr connect instance
client.set_signer(signer).await;
}
Err(e) => {
log::warn!("Nostr Connect instance (QR Code) is timeout. TODO: fix this");
this.update_in(cx, |_, window, cx| {
window.push_notification(
Notification::error(e.to_string()).title("Nostr Connect"),
cx,
);
})
.ok();
}
};
}
})
.detach();
// Set the client's signer with the current nostr connect instance
client.set_signer(signer).await;
}
Err(e) => {
this.update_in(cx, |_, window, cx| {
window.push_notification(
Notification::error(e.to_string()).title("Nostr Connect"),
cx,
);
})
.ok();
}
};
}
}),
)
}
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 settings::AppSettings;
use theme::ActiveTheme;
use ui::actions::OpenProfile;
use ui::avatar::Avatar;
use ui::context_menu::ContextMenuExt;
use ui::modal::ModalButtonProps;
use ui::skeleton::Skeleton;
use ui::{h_flex, ContextModal, StyledExt};
@@ -167,10 +165,6 @@ impl RenderOnce for RoomListItem {
.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))
.on_click(move |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
}
pub fn is_authenticated_relays(&self, url: &RelayUrl) -> bool {
pub fn is_authenticated(&self, url: &RelayUrl) -> bool {
self.setting_values.authenticated_relays.contains(url)
}
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
.authenticated_relays
.push(relay_url.to_owned());