introduce proper error boundary
This commit is contained in:
50
src/error.rs
Normal file
50
src/error.rs
Normal file
@ -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<W: IsA<gtk::Widget>> ErrorBoundaryProvider for W {
|
||||
fn error_boundary(&self) -> ErrorBoundary {
|
||||
let direct_ancestor: Option<adw::ToastOverlay> = self
|
||||
.ancestor(adw::ToastOverlay::static_type())
|
||||
.and_downcast();
|
||||
let win: Option<adw::ToastOverlay> = 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<adw::ToastOverlay>,
|
||||
}
|
||||
|
||||
impl ErrorBoundary {
|
||||
pub fn spawn<T>(self, f: impl Future<Output = Result<T, Error>> + '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);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,7 @@ mod application;
|
||||
#[rustfmt::skip]
|
||||
mod config;
|
||||
mod async_utils;
|
||||
pub mod error;
|
||||
mod subscription;
|
||||
pub mod widgets;
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
});
|
||||
|
||||
@ -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<gtk::Editable>) {
|
||||
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 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<T, R: std::fmt::Display>(
|
||||
&self,
|
||||
f: impl Future<Output = Result<T, R>> + 'static,
|
||||
);
|
||||
}
|
||||
|
||||
impl<W: IsA<gtk::Widget>> SpawnWithToast for W {
|
||||
fn spawn_with_near_toast<T, R: std::fmt::Display>(
|
||||
&self,
|
||||
f: impl Future<Output = Result<T, R>> + 'static,
|
||||
) {
|
||||
let toast_overlay: Option<adw::ToastOverlay> = self
|
||||
.ancestor(adw::ToastOverlay::static_type())
|
||||
.and_downcast();
|
||||
let win: Option<NotifyWindow> = 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()));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user