Skip to content

Commit 29377dc

Browse files
committed
introduce proper error boundary
1 parent 75c59df commit 29377dc

File tree

8 files changed

+88
-62
lines changed

8 files changed

+88
-62
lines changed

src/error.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use futures::Future;
2+
use glib::subclass::prelude::*;
3+
use gtk::prelude::*;
4+
use gtk::{self, glib};
5+
6+
use crate::widgets::NotifyWindow;
7+
8+
pub type Error = anyhow::Error;
9+
10+
pub trait ErrorBoundaryProvider {
11+
fn error_boundary(&self) -> ErrorBoundary;
12+
}
13+
14+
impl<W: IsA<gtk::Widget>> ErrorBoundaryProvider for W {
15+
fn error_boundary(&self) -> ErrorBoundary {
16+
let direct_ancestor: Option<adw::ToastOverlay> = self
17+
.ancestor(adw::ToastOverlay::static_type())
18+
.and_downcast();
19+
let win: Option<adw::ToastOverlay> = self
20+
.ancestor(NotifyWindow::static_type())
21+
.and_downcast()
22+
.map(|win: NotifyWindow| win.imp().toast_overlay.clone());
23+
let toast_overlay = direct_ancestor.or(win);
24+
ErrorBoundary {
25+
source: self.clone().into(),
26+
boundary: toast_overlay,
27+
}
28+
}
29+
}
30+
31+
pub struct ErrorBoundary {
32+
source: gtk::Widget,
33+
boundary: Option<adw::ToastOverlay>,
34+
}
35+
36+
impl ErrorBoundary {
37+
pub fn spawn<T>(self, f: impl Future<Output = Result<T, Error>> + 'static) {
38+
glib::MainContext::ref_thread_default().spawn_local_with_priority(
39+
glib::Priority::DEFAULT_IDLE,
40+
async move {
41+
if let Err(e) = f.await {
42+
if let Some(boundary) = self.boundary {
43+
boundary.add_toast(adw::Toast::builder().title(&e.to_string()).build());
44+
}
45+
tracing::error!(source=?self.source.type_().name(), error=?e);
46+
}
47+
},
48+
);
49+
}
50+
}

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ mod application;
22
#[rustfmt::skip]
33
mod config;
44
mod async_utils;
5+
pub mod error;
56
mod subscription;
67
pub mod widgets;
78

src/subscription.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ impl Subscription {
200200
self.notify_display_name();
201201
}
202202
#[instrument(skip_all)]
203-
pub fn set_display_name(&self, value: String) -> Promise<(), capnp::Error> {
203+
pub fn set_display_name(&self, value: String) -> Promise<(), anyhow::Error> {
204204
let this = self.clone();
205205
Promise::from_future(async move {
206206
this._set_display_name(value);
@@ -209,7 +209,7 @@ impl Subscription {
209209
})
210210
}
211211

212-
fn send_updated_info(&self) -> Promise<(), capnp::Error> {
212+
fn send_updated_info(&self) -> Promise<(), anyhow::Error> {
213213
let imp = self.imp();
214214
let mut req = imp.client.get().unwrap().update_info_request();
215215
let mut val = pry!(req.get().get_value());
@@ -240,7 +240,7 @@ impl Subscription {
240240
self.notify_unread_count();
241241
}
242242

243-
pub fn set_muted(&self, value: bool) -> Promise<(), capnp::Error> {
243+
pub fn set_muted(&self, value: bool) -> Promise<(), anyhow::Error> {
244244
let this = self.clone();
245245
Promise::from_future(async move {
246246
this.imp().muted.replace(value);
@@ -249,7 +249,7 @@ impl Subscription {
249249
Ok(())
250250
})
251251
}
252-
pub fn flag_all_as_read(&self) -> Promise<(), capnp::Error> {
252+
pub fn flag_all_as_read(&self) -> Promise<(), anyhow::Error> {
253253
let imp = self.imp();
254254
let Some(value) = Self::last_message(&imp.messages)
255255
.map(|last| last.time)
@@ -268,11 +268,11 @@ impl Subscription {
268268
Ok(())
269269
})
270270
}
271-
pub fn publish_msg(&self, mut msg: models::Message) -> Promise<(), capnp::Error> {
271+
pub fn publish_msg(&self, mut msg: models::Message) -> Promise<(), anyhow::Error> {
272272
let imp = self.imp();
273273
let json = {
274274
msg.topic = self.topic();
275-
serde_json::to_string(&msg).map_err(|e| capnp::Error::failed(e.to_string()))
275+
serde_json::to_string(&msg)
276276
};
277277
let mut req = imp.client.get().unwrap().publish_request();
278278
req.get().set_message(pry!(json).as_str().into());
@@ -284,7 +284,7 @@ impl Subscription {
284284
})
285285
}
286286
#[instrument(skip_all)]
287-
pub fn clear_notifications(&self) -> Promise<(), capnp::Error> {
287+
pub fn clear_notifications(&self) -> Promise<(), anyhow::Error> {
288288
let imp = self.imp();
289289
let req = imp.client.get().unwrap().clear_notifications_request();
290290
let this = self.clone();

src/widgets/advanced_message_dialog.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ use adw::subclass::prelude::*;
55
use gsv::prelude::*;
66
use gtk::{gio, glib};
77

8+
use crate::error::*;
89
use crate::subscription::Subscription;
9-
use crate::widgets::*;
1010

1111
mod imp {
1212
use super::*;
@@ -194,7 +194,7 @@ impl AdvancedMessageDialog {
194194
thisc.imp().subscription.get().unwrap()
195195
.publish_msg(msg).await
196196
};
197-
toast_overlay.spawn_with_near_toast(f);
197+
toast_overlay.error_boundary().spawn(f);
198198
}
199199
}
200200
}

src/widgets/message_row.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use gtk::{gdk, gio, glib};
77
use ntfy_daemon::models;
88
use tracing::error;
99

10-
use crate::widgets::window::SpawnWithToast;
10+
use crate::error::*;
1111

1212
mod imp {
1313
use super::*;
@@ -177,10 +177,10 @@ impl MessageRow {
177177
picture.set_height_request(350);
178178
let picturec = picture.clone();
179179

180-
self.spawn_with_near_toast(async move {
180+
self.error_boundary().spawn(async move {
181181
let t = r.recv().await?;
182182
picturec.set_paintable(Some(&t));
183-
Ok::<(), anyhow::Error>(())
183+
Ok(())
184184
});
185185

186186
picture

src/widgets/preferences.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use adw::subclass::prelude::*;
55
use gtk::{gio, glib};
66
use ntfy_daemon::ntfy_capnp::system_notifier;
77

8-
use crate::widgets::*;
8+
use crate::error::*;
99

1010
mod imp {
1111
use super::*;
@@ -90,12 +90,14 @@ impl NotifyPreferences {
9090
let this = obj.clone();
9191
obj.imp().add_btn.connect_clicked(move |btn| {
9292
let this = this.clone();
93-
btn.spawn_with_near_toast(async move { this.add_account().await });
93+
btn.error_boundary()
94+
.spawn(async move { this.add_account().await });
9495
});
9596
let this = obj.clone();
9697
obj.imp()
9798
.added_accounts
98-
.spawn_with_near_toast(async move { this.show_accounts().await });
99+
.error_boundary()
100+
.spawn(async move { this.show_accounts().await });
99101
obj
100102
}
101103

@@ -128,9 +130,8 @@ impl NotifyPreferences {
128130
let this = this.clone();
129131
let username = username.clone();
130132
let server = server.clone();
131-
btn.spawn_with_near_toast(async move {
132-
this.remove_account(&server, &username).await
133-
});
133+
btn.error_boundary()
134+
.spawn(async move { this.remove_account(&server, &username).await });
134135
});
135136
btn
136137
});

src/widgets/subscription_info_dialog.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use glib::Properties;
66
use gtk::gio;
77
use gtk::glib;
88

9-
use crate::widgets::*;
9+
use crate::error::*;
1010

1111
mod imp {
1212
pub use super::*;
@@ -91,7 +91,7 @@ impl SubscriptionInfoDialog {
9191
fn update_display_name(&self, entry: &impl IsA<gtk::Editable>) {
9292
if let Some(sub) = self.subscription() {
9393
let entry = entry.clone();
94-
self.spawn_with_near_toast(async move {
94+
self.error_boundary().spawn(async move {
9595
let res = sub.set_display_name(entry.text().to_string()).await;
9696
res
9797
});
@@ -100,7 +100,8 @@ impl SubscriptionInfoDialog {
100100
fn update_muted(&self, switch: &adw::SwitchRow) {
101101
if let Some(sub) = self.subscription() {
102102
let switch = switch.clone();
103-
self.spawn_with_near_toast(async move { sub.set_muted(switch.is_active()).await })
103+
self.error_boundary()
104+
.spawn(async move { sub.set_muted(switch.is_active()).await })
104105
}
105106
}
106107
}

src/widgets/window.rs

Lines changed: 14 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -11,41 +11,10 @@ use tracing::warn;
1111

1212
use crate::application::NotifyApplication;
1313
use crate::config::{APP_ID, PROFILE};
14+
use crate::error::*;
1415
use crate::subscription::Subscription;
1516
use crate::widgets::*;
1617

17-
pub trait SpawnWithToast {
18-
fn spawn_with_near_toast<T, R: std::fmt::Display>(
19-
&self,
20-
f: impl Future<Output = Result<T, R>> + 'static,
21-
);
22-
}
23-
24-
impl<W: IsA<gtk::Widget>> SpawnWithToast for W {
25-
fn spawn_with_near_toast<T, R: std::fmt::Display>(
26-
&self,
27-
f: impl Future<Output = Result<T, R>> + 'static,
28-
) {
29-
let toast_overlay: Option<adw::ToastOverlay> = self
30-
.ancestor(adw::ToastOverlay::static_type())
31-
.and_downcast();
32-
let win: Option<NotifyWindow> = self.ancestor(NotifyWindow::static_type()).and_downcast();
33-
glib::MainContext::ref_thread_default().spawn_local_with_priority(
34-
glib::Priority::DEFAULT_IDLE,
35-
async move {
36-
if let Err(e) = f.await {
37-
if let Some(o) = toast_overlay
38-
.as_ref()
39-
.or_else(|| win.as_ref().map(|win| win.imp().toast_overlay.as_ref()))
40-
{
41-
o.add_toast(adw::Toast::builder().title(&e.to_string()).build())
42-
}
43-
}
44-
},
45-
);
46-
}
47-
}
48-
4918
mod imp {
5019
use super::*;
5120

@@ -169,7 +138,7 @@ mod imp {
169138
});
170139
klass.install_action("win.clear-notifications", None, |this, _, _| {
171140
this.selected_subscription().map(|sub| {
172-
this.spawn_with_near_toast(sub.clear_notifications());
141+
this.error_boundary().spawn(sub.clear_notifications());
173142
});
174143
});
175144
//klass.bind_template_instance_callbacks();
@@ -252,7 +221,10 @@ impl NotifyWindow {
252221
..models::Message::default()
253222
});
254223

255-
entry.spawn_with_near_toast(async move { p.await });
224+
entry.error_boundary().spawn(async move {
225+
p.await?;
226+
Ok(())
227+
});
256228
};
257229
let publishc = publish.clone();
258230
imp.entry.connect_activate(move |_| publishc());
@@ -294,7 +266,7 @@ impl NotifyWindow {
294266
req.get().set_topic(sub.topic.as_str().into());
295267
let res = req.send();
296268
let this = self.clone();
297-
self.spawn_with_near_toast(async move {
269+
self.error_boundary().spawn(async move {
298270
let imp = this.imp();
299271

300272
// Subscription::new will use the pipelined client to retrieve info about the subscription
@@ -306,7 +278,7 @@ impl NotifyWindow {
306278
let i = imp.subscription_list_model.n_items() - 1;
307279
let row = imp.subscription_list.row_at_index(i as i32);
308280
imp.subscription_list.select_row(row.as_ref());
309-
Ok::<(), capnp::Error>(())
281+
Ok(())
310282
});
311283
}
312284

@@ -320,14 +292,14 @@ impl NotifyWindow {
320292
let res = req.send();
321293
let this = self.clone();
322294

323-
self.spawn_with_near_toast(async move {
295+
self.error_boundary().spawn(async move {
324296
let imp = this.imp();
325297
res.promise.await?;
326298

327299
if let Some(i) = imp.subscription_list_model.find(&sub) {
328300
imp.subscription_list_model.remove(i);
329301
}
330-
Ok::<(), capnp::Error>(())
302+
Ok(())
331303
});
332304
}
333305
fn notifier(&self) -> &system_notifier::Client {
@@ -358,14 +330,14 @@ impl NotifyWindow {
358330
let this = self.clone();
359331
let req = self.notifier().list_subscriptions_request();
360332
let res = req.send();
361-
self.spawn_with_near_toast(async move {
333+
self.error_boundary().spawn(async move {
362334
let list = res.promise.await?;
363335
let list = list.get()?.get_list()?;
364336
let imp = this.imp();
365337
for sub in list {
366338
imp.subscription_list_model.append(&Subscription::new(sub?));
367339
}
368-
Ok::<(), capnp::Error>(())
340+
Ok(())
369341
});
370342
}
371343
fn update_banner(&self, sub: Option<&Subscription>) {
@@ -429,7 +401,8 @@ impl NotifyWindow {
429401
|| ((vadj.page_size() + vadj.value() - vadj.upper()).abs() <= 1.0)
430402
{
431403
self.selected_subscription().map(|sub| {
432-
self.spawn_with_near_toast(sub.flag_all_as_read());
404+
self.error_boundary()
405+
.spawn(sub.flag_all_as_read().map_err(|e| e.into()));
433406
});
434407
}
435408
}

0 commit comments

Comments
 (0)