From 29377dc1a6d528de98de59abeeaff5f09d3ea372 Mon Sep 17 00:00:00 2001 From: ranfdev Date: Tue, 13 Feb 2024 15:41:23 +0100 Subject: [PATCH] introduce proper error boundary --- src/error.rs | 50 ++++++++++++++++++++++ src/main.rs | 1 + src/subscription.rs | 14 +++---- src/widgets/advanced_message_dialog.rs | 4 +- src/widgets/message_row.rs | 6 +-- src/widgets/preferences.rs | 13 +++--- src/widgets/subscription_info_dialog.rs | 7 ++-- src/widgets/window.rs | 55 +++++++------------------ 8 files changed, 88 insertions(+), 62 deletions(-) create mode 100644 src/error.rs diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..95ab659 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,50 @@ +use futures::Future; +use glib::subclass::prelude::*; +use gtk::prelude::*; +use gtk::{self, glib}; + +use crate::widgets::NotifyWindow; + +pub type Error = anyhow::Error; + +pub trait ErrorBoundaryProvider { + fn error_boundary(&self) -> ErrorBoundary; +} + +impl> ErrorBoundaryProvider for W { + fn error_boundary(&self) -> ErrorBoundary { + let direct_ancestor: Option = self + .ancestor(adw::ToastOverlay::static_type()) + .and_downcast(); + let win: Option = self + .ancestor(NotifyWindow::static_type()) + .and_downcast() + .map(|win: NotifyWindow| win.imp().toast_overlay.clone()); + let toast_overlay = direct_ancestor.or(win); + ErrorBoundary { + source: self.clone().into(), + boundary: toast_overlay, + } + } +} + +pub struct ErrorBoundary { + source: gtk::Widget, + boundary: Option, +} + +impl ErrorBoundary { + pub fn spawn(self, f: impl Future> + 'static) { + glib::MainContext::ref_thread_default().spawn_local_with_priority( + glib::Priority::DEFAULT_IDLE, + async move { + if let Err(e) = f.await { + if let Some(boundary) = self.boundary { + boundary.add_toast(adw::Toast::builder().title(&e.to_string()).build()); + } + tracing::error!(source=?self.source.type_().name(), error=?e); + } + }, + ); + } +} diff --git a/src/main.rs b/src/main.rs index b7bf074..2149993 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ mod application; #[rustfmt::skip] mod config; mod async_utils; +pub mod error; mod subscription; pub mod widgets; diff --git a/src/subscription.rs b/src/subscription.rs index dc93170..e161e6d 100644 --- a/src/subscription.rs +++ b/src/subscription.rs @@ -200,7 +200,7 @@ impl Subscription { self.notify_display_name(); } #[instrument(skip_all)] - pub fn set_display_name(&self, value: String) -> Promise<(), capnp::Error> { + pub fn set_display_name(&self, value: String) -> Promise<(), anyhow::Error> { let this = self.clone(); Promise::from_future(async move { this._set_display_name(value); @@ -209,7 +209,7 @@ impl Subscription { }) } - fn send_updated_info(&self) -> Promise<(), capnp::Error> { + fn send_updated_info(&self) -> Promise<(), anyhow::Error> { let imp = self.imp(); let mut req = imp.client.get().unwrap().update_info_request(); let mut val = pry!(req.get().get_value()); @@ -240,7 +240,7 @@ impl Subscription { self.notify_unread_count(); } - pub fn set_muted(&self, value: bool) -> Promise<(), capnp::Error> { + pub fn set_muted(&self, value: bool) -> Promise<(), anyhow::Error> { let this = self.clone(); Promise::from_future(async move { this.imp().muted.replace(value); @@ -249,7 +249,7 @@ impl Subscription { Ok(()) }) } - pub fn flag_all_as_read(&self) -> Promise<(), capnp::Error> { + pub fn flag_all_as_read(&self) -> Promise<(), anyhow::Error> { let imp = self.imp(); let Some(value) = Self::last_message(&imp.messages) .map(|last| last.time) @@ -268,11 +268,11 @@ impl Subscription { Ok(()) }) } - pub fn publish_msg(&self, mut msg: models::Message) -> Promise<(), capnp::Error> { + pub fn publish_msg(&self, mut msg: models::Message) -> Promise<(), anyhow::Error> { let imp = self.imp(); let json = { msg.topic = self.topic(); - serde_json::to_string(&msg).map_err(|e| capnp::Error::failed(e.to_string())) + serde_json::to_string(&msg) }; let mut req = imp.client.get().unwrap().publish_request(); req.get().set_message(pry!(json).as_str().into()); @@ -284,7 +284,7 @@ impl Subscription { }) } #[instrument(skip_all)] - pub fn clear_notifications(&self) -> Promise<(), capnp::Error> { + pub fn clear_notifications(&self) -> Promise<(), anyhow::Error> { let imp = self.imp(); let req = imp.client.get().unwrap().clear_notifications_request(); let this = self.clone(); diff --git a/src/widgets/advanced_message_dialog.rs b/src/widgets/advanced_message_dialog.rs index 9271e77..26a6bbc 100644 --- a/src/widgets/advanced_message_dialog.rs +++ b/src/widgets/advanced_message_dialog.rs @@ -5,8 +5,8 @@ use adw::subclass::prelude::*; use gsv::prelude::*; use gtk::{gio, glib}; +use crate::error::*; use crate::subscription::Subscription; -use crate::widgets::*; mod imp { use super::*; @@ -194,7 +194,7 @@ impl AdvancedMessageDialog { thisc.imp().subscription.get().unwrap() .publish_msg(msg).await }; - toast_overlay.spawn_with_near_toast(f); + toast_overlay.error_boundary().spawn(f); } } } diff --git a/src/widgets/message_row.rs b/src/widgets/message_row.rs index faf41c9..8f30606 100644 --- a/src/widgets/message_row.rs +++ b/src/widgets/message_row.rs @@ -7,7 +7,7 @@ use gtk::{gdk, gio, glib}; use ntfy_daemon::models; use tracing::error; -use crate::widgets::window::SpawnWithToast; +use crate::error::*; mod imp { use super::*; @@ -177,10 +177,10 @@ impl MessageRow { picture.set_height_request(350); let picturec = picture.clone(); - self.spawn_with_near_toast(async move { + self.error_boundary().spawn(async move { let t = r.recv().await?; picturec.set_paintable(Some(&t)); - Ok::<(), anyhow::Error>(()) + Ok(()) }); picture diff --git a/src/widgets/preferences.rs b/src/widgets/preferences.rs index 1de5ccd..2ed3a34 100644 --- a/src/widgets/preferences.rs +++ b/src/widgets/preferences.rs @@ -5,7 +5,7 @@ use adw::subclass::prelude::*; use gtk::{gio, glib}; use ntfy_daemon::ntfy_capnp::system_notifier; -use crate::widgets::*; +use crate::error::*; mod imp { use super::*; @@ -90,12 +90,14 @@ impl NotifyPreferences { let this = obj.clone(); obj.imp().add_btn.connect_clicked(move |btn| { let this = this.clone(); - btn.spawn_with_near_toast(async move { this.add_account().await }); + btn.error_boundary() + .spawn(async move { this.add_account().await }); }); let this = obj.clone(); obj.imp() .added_accounts - .spawn_with_near_toast(async move { this.show_accounts().await }); + .error_boundary() + .spawn(async move { this.show_accounts().await }); obj } @@ -128,9 +130,8 @@ impl NotifyPreferences { let this = this.clone(); let username = username.clone(); let server = server.clone(); - btn.spawn_with_near_toast(async move { - this.remove_account(&server, &username).await - }); + btn.error_boundary() + .spawn(async move { this.remove_account(&server, &username).await }); }); btn }); diff --git a/src/widgets/subscription_info_dialog.rs b/src/widgets/subscription_info_dialog.rs index c4dc5d3..4ab03e5 100644 --- a/src/widgets/subscription_info_dialog.rs +++ b/src/widgets/subscription_info_dialog.rs @@ -6,7 +6,7 @@ use glib::Properties; use gtk::gio; use gtk::glib; -use crate::widgets::*; +use crate::error::*; mod imp { pub use super::*; @@ -91,7 +91,7 @@ impl SubscriptionInfoDialog { fn update_display_name(&self, entry: &impl IsA) { if let Some(sub) = self.subscription() { let entry = entry.clone(); - self.spawn_with_near_toast(async move { + self.error_boundary().spawn(async move { let res = sub.set_display_name(entry.text().to_string()).await; res }); @@ -100,7 +100,8 @@ impl SubscriptionInfoDialog { fn update_muted(&self, switch: &adw::SwitchRow) { if let Some(sub) = self.subscription() { let switch = switch.clone(); - self.spawn_with_near_toast(async move { sub.set_muted(switch.is_active()).await }) + self.error_boundary() + .spawn(async move { sub.set_muted(switch.is_active()).await }) } } } diff --git a/src/widgets/window.rs b/src/widgets/window.rs index f912028..3adc700 100644 --- a/src/widgets/window.rs +++ b/src/widgets/window.rs @@ -11,41 +11,10 @@ use tracing::warn; use crate::application::NotifyApplication; use crate::config::{APP_ID, PROFILE}; +use crate::error::*; use crate::subscription::Subscription; use crate::widgets::*; -pub trait SpawnWithToast { - fn spawn_with_near_toast( - &self, - f: impl Future> + 'static, - ); -} - -impl> SpawnWithToast for W { - fn spawn_with_near_toast( - &self, - f: impl Future> + 'static, - ) { - let toast_overlay: Option = self - .ancestor(adw::ToastOverlay::static_type()) - .and_downcast(); - let win: Option = self.ancestor(NotifyWindow::static_type()).and_downcast(); - glib::MainContext::ref_thread_default().spawn_local_with_priority( - glib::Priority::DEFAULT_IDLE, - async move { - if let Err(e) = f.await { - if let Some(o) = toast_overlay - .as_ref() - .or_else(|| win.as_ref().map(|win| win.imp().toast_overlay.as_ref())) - { - o.add_toast(adw::Toast::builder().title(&e.to_string()).build()) - } - } - }, - ); - } -} - mod imp { use super::*; @@ -169,7 +138,7 @@ mod imp { }); klass.install_action("win.clear-notifications", None, |this, _, _| { this.selected_subscription().map(|sub| { - this.spawn_with_near_toast(sub.clear_notifications()); + this.error_boundary().spawn(sub.clear_notifications()); }); }); //klass.bind_template_instance_callbacks(); @@ -252,7 +221,10 @@ impl NotifyWindow { ..models::Message::default() }); - entry.spawn_with_near_toast(async move { p.await }); + entry.error_boundary().spawn(async move { + p.await?; + Ok(()) + }); }; let publishc = publish.clone(); imp.entry.connect_activate(move |_| publishc()); @@ -294,7 +266,7 @@ impl NotifyWindow { req.get().set_topic(sub.topic.as_str().into()); let res = req.send(); let this = self.clone(); - self.spawn_with_near_toast(async move { + self.error_boundary().spawn(async move { let imp = this.imp(); // Subscription::new will use the pipelined client to retrieve info about the subscription @@ -306,7 +278,7 @@ impl NotifyWindow { let i = imp.subscription_list_model.n_items() - 1; let row = imp.subscription_list.row_at_index(i as i32); imp.subscription_list.select_row(row.as_ref()); - Ok::<(), capnp::Error>(()) + Ok(()) }); } @@ -320,14 +292,14 @@ impl NotifyWindow { let res = req.send(); let this = self.clone(); - self.spawn_with_near_toast(async move { + self.error_boundary().spawn(async move { let imp = this.imp(); res.promise.await?; if let Some(i) = imp.subscription_list_model.find(&sub) { imp.subscription_list_model.remove(i); } - Ok::<(), capnp::Error>(()) + Ok(()) }); } fn notifier(&self) -> &system_notifier::Client { @@ -358,14 +330,14 @@ impl NotifyWindow { let this = self.clone(); let req = self.notifier().list_subscriptions_request(); let res = req.send(); - self.spawn_with_near_toast(async move { + self.error_boundary().spawn(async move { let list = res.promise.await?; let list = list.get()?.get_list()?; let imp = this.imp(); for sub in list { imp.subscription_list_model.append(&Subscription::new(sub?)); } - Ok::<(), capnp::Error>(()) + Ok(()) }); } fn update_banner(&self, sub: Option<&Subscription>) { @@ -429,7 +401,8 @@ impl NotifyWindow { || ((vadj.page_size() + vadj.value() - vadj.upper()).abs() <= 1.0) { self.selected_subscription().map(|sub| { - self.spawn_with_near_toast(sub.flag_all_as_read()); + self.error_boundary() + .spawn(sub.flag_all_as_read().map_err(|e| e.into())); }); } }