Handle all user errors in add_subscription_dialog
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -1641,7 +1641,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "notify"
|
name = "notify"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"ashpd",
|
"ashpd",
|
||||||
|
|||||||
@ -21,6 +21,8 @@ pub struct SharedEnv {
|
|||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("topic {0} must not be empty and must contain only alphanumeric characters and _ (underscore)")]
|
#[error("topic {0} must not be empty and must contain only alphanumeric characters and _ (underscore)")]
|
||||||
InvalidTopic(String),
|
InvalidTopic(String),
|
||||||
|
#[error("invalid server base url {0:?}")]
|
||||||
|
InvalidServer(#[from] url::ParseError),
|
||||||
#[error("duplicate message")]
|
#[error("duplicate message")]
|
||||||
DuplicateMessage,
|
DuplicateMessage,
|
||||||
#[error("can't parse the minimum set of required fields from the message {0}")]
|
#[error("can't parse the minimum set of required fields from the message {0}")]
|
||||||
|
|||||||
@ -121,23 +121,31 @@ pub struct Subscription {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Subscription {
|
impl Subscription {
|
||||||
pub fn build_url(server: &str, topic: &str, since: u64) -> anyhow::Result<url::Url> {
|
pub fn build_url(server: &str, topic: &str, since: u64) -> Result<url::Url, crate::Error> {
|
||||||
let mut url = url::Url::parse(server)?;
|
let mut url = url::Url::parse(server)?;
|
||||||
url.path_segments_mut()
|
url.path_segments_mut()
|
||||||
.map_err(|_| anyhow::anyhow!("url can't be base"))?
|
.map_err(|_| url::ParseError::RelativeUrlWithCannotBeABaseBase)?
|
||||||
.push(&topic)
|
.push(&topic)
|
||||||
.push("json");
|
.push("json");
|
||||||
url.query_pairs_mut()
|
url.query_pairs_mut()
|
||||||
.append_pair("since", &since.to_string());
|
.append_pair("since", &since.to_string());
|
||||||
Ok(url)
|
Ok(url)
|
||||||
}
|
}
|
||||||
pub fn validate(self) -> anyhow::Result<Self> {
|
pub fn validate(self) -> Result<Self, Vec<crate::Error>> {
|
||||||
validate_topic(&self.topic)?;
|
let mut errs = vec![];
|
||||||
Self::build_url(&self.server, &self.topic, 0)?;
|
if let Err(e) = validate_topic(&self.topic) {
|
||||||
|
errs.push(e);
|
||||||
|
};
|
||||||
|
if let Err(e) = Self::build_url(&self.server, &self.topic, 0) {
|
||||||
|
errs.push(e);
|
||||||
|
};
|
||||||
|
if errs.len() > 0 {
|
||||||
|
return Err(errs);
|
||||||
|
}
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
pub fn builder(server: String, topic: String) -> SubscriptionBuilder {
|
pub fn builder(topic: String) -> SubscriptionBuilder {
|
||||||
SubscriptionBuilder::new(server, topic)
|
SubscriptionBuilder::new(topic)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,9 +161,9 @@ pub struct SubscriptionBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SubscriptionBuilder {
|
impl SubscriptionBuilder {
|
||||||
pub fn new(server: String, topic: String) -> Self {
|
pub fn new(topic: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
server,
|
server: "https://ntfy.sh".to_string(),
|
||||||
topic,
|
topic,
|
||||||
muted: false,
|
muted: false,
|
||||||
archived: false,
|
archived: false,
|
||||||
@ -165,6 +173,11 @@ impl SubscriptionBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn server(mut self, server: String) -> Self {
|
||||||
|
self.server = server;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn muted(mut self, muted: bool) -> Self {
|
pub fn muted(mut self, muted: bool) -> Self {
|
||||||
self.muted = muted;
|
self.muted = muted;
|
||||||
self
|
self
|
||||||
@ -190,7 +203,7 @@ impl SubscriptionBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(self) -> anyhow::Result<Subscription> {
|
pub fn build(self) -> Result<Subscription, Vec<Error>> {
|
||||||
let res = Subscription {
|
let res = Subscription {
|
||||||
server: self.server,
|
server: self.server,
|
||||||
topic: self.topic,
|
topic: self.topic,
|
||||||
|
|||||||
@ -401,17 +401,11 @@ impl system_notifier::Server for SystemNotifier {
|
|||||||
) -> capnp::capability::Promise<(), capnp::Error> {
|
) -> capnp::capability::Promise<(), capnp::Error> {
|
||||||
let topic = pry!(pry!(params.get()).get_topic());
|
let topic = pry!(pry!(params.get()).get_topic());
|
||||||
let server: &str = pry!(pry!(params.get()).get_server());
|
let server: &str = pry!(pry!(params.get()).get_server());
|
||||||
let server = if server.is_empty() {
|
|
||||||
"https://ntfy.sh"
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
};
|
|
||||||
|
|
||||||
let subscription = pry!(
|
let subscription = pry!(models::Subscription::builder(topic.to_owned())
|
||||||
models::Subscription::builder(server.to_owned(), topic.to_owned())
|
.server(server.to_string())
|
||||||
.build()
|
.build()
|
||||||
.map_err(|e| capnp::Error::failed(e.to_string()))
|
.map_err(|e| capnp::Error::failed(format!("{:?}", e))));
|
||||||
);
|
|
||||||
let sub: Promise<subscription::Client, capnp::Error> = self.watch(subscription.clone());
|
let sub: Promise<subscription::Client, capnp::Error> = self.watch(subscription.clone());
|
||||||
|
|
||||||
let mut db = self.env.db.clone();
|
let mut db = self.env.db.clone();
|
||||||
|
|||||||
@ -8,13 +8,18 @@ use gtk::gio;
|
|||||||
use gtk::glib;
|
use gtk::glib;
|
||||||
use ntfy_daemon::models;
|
use ntfy_daemon::models;
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone)]
|
||||||
|
pub struct Widgets {
|
||||||
|
pub topic_entry: adw::EntryRow,
|
||||||
|
pub server_entry: adw::EntryRow,
|
||||||
|
pub server_expander: adw::ExpanderRow,
|
||||||
|
pub sub_btn: gtk::Button,
|
||||||
|
}
|
||||||
mod imp {
|
mod imp {
|
||||||
pub use super::*;
|
pub use super::*;
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct AddSubscriptionDialog {
|
pub struct AddSubscriptionDialog {
|
||||||
pub topic_entry: RefCell<adw::EntryRow>,
|
pub widgets: RefCell<Widgets>,
|
||||||
pub server_entry: RefCell<adw::EntryRow>,
|
|
||||||
pub sub_btn: RefCell<gtk::Button>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[glib::object_subclass]
|
#[glib::object_subclass]
|
||||||
@ -32,7 +37,6 @@ mod imp {
|
|||||||
);
|
);
|
||||||
klass.install_action("default.activate", None, |this, _, _| {
|
klass.install_action("default.activate", None, |this, _, _| {
|
||||||
this.emit_subscribe_request();
|
this.emit_subscribe_request();
|
||||||
this.close();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -112,7 +116,7 @@ impl AddSubscriptionDialog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
append = &adw::ExpanderRow {
|
append: server_expander = &adw::ExpanderRow {
|
||||||
set_title: "Custom server...",
|
set_title: "Custom server...",
|
||||||
set_enable_expansion: false,
|
set_enable_expansion: false,
|
||||||
set_show_enable_switch: true,
|
set_show_enable_switch: true,
|
||||||
@ -129,7 +133,6 @@ impl AddSubscriptionDialog {
|
|||||||
set_sensitive: false,
|
set_sensitive: false,
|
||||||
connect_clicked[obj] => move |_| {
|
connect_clicked[obj] => move |_| {
|
||||||
obj.emit_subscribe_request();
|
obj.emit_subscribe_request();
|
||||||
obj.close();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -142,34 +145,58 @@ impl AddSubscriptionDialog {
|
|||||||
topic_entry.delegate().unwrap().connect_changed(move |_| {
|
topic_entry.delegate().unwrap().connect_changed(move |_| {
|
||||||
txc.send(()).unwrap();
|
txc.send(()).unwrap();
|
||||||
});
|
});
|
||||||
|
let txc = tx.clone();
|
||||||
|
server_entry.delegate().unwrap().connect_changed(move |_| {
|
||||||
|
txc.send(()).unwrap();
|
||||||
|
});
|
||||||
|
server_expander.connect_enable_expansion_notify(move |_| {
|
||||||
|
tx.send(()).unwrap();
|
||||||
|
});
|
||||||
let rx = crate::async_utils::debounce_channel(std::time::Duration::from_millis(500), rx);
|
let rx = crate::async_utils::debounce_channel(std::time::Duration::from_millis(500), rx);
|
||||||
let objc = obj.clone();
|
let objc = obj.clone();
|
||||||
rx.attach(None, move |_| {
|
rx.attach(None, move |_| {
|
||||||
objc.check_errors();
|
objc.check_errors();
|
||||||
glib::ControlFlow::Continue
|
glib::ControlFlow::Continue
|
||||||
});
|
});
|
||||||
imp.topic_entry.replace(topic_entry);
|
imp.widgets.replace(Widgets {
|
||||||
imp.server_entry.replace(server_entry);
|
topic_entry,
|
||||||
imp.sub_btn.replace(sub_btn);
|
server_expander,
|
||||||
|
server_entry,
|
||||||
|
sub_btn,
|
||||||
|
});
|
||||||
|
|
||||||
obj.set_content(Some(&toolbar_view));
|
obj.set_content(Some(&toolbar_view));
|
||||||
}
|
}
|
||||||
pub fn topic(&self) -> String {
|
pub fn subscription(&self) -> Result<models::Subscription, Vec<ntfy_daemon::Error>> {
|
||||||
self.imp().topic_entry.borrow().text().to_string()
|
let w = { self.imp().widgets.borrow().clone() };
|
||||||
|
let mut sub = models::Subscription::builder(w.topic_entry.text().to_string());
|
||||||
|
if w.server_expander.enables_expansion() {
|
||||||
|
sub = sub.server(w.server_entry.text().to_string());
|
||||||
}
|
}
|
||||||
pub fn server(&self) -> String {
|
|
||||||
self.imp().server_entry.borrow().text().to_string()
|
sub.build()
|
||||||
}
|
}
|
||||||
fn check_errors(&self) {
|
fn check_errors(&self) {
|
||||||
let imp = self.imp();
|
let w = { self.imp().widgets.borrow().clone() };
|
||||||
let topic_entry = imp.topic_entry.borrow().clone();
|
let sub = self.subscription();
|
||||||
let sub_btn = imp.sub_btn.borrow().clone();
|
|
||||||
if let Err(_) = models::validate_topic(&topic_entry.delegate().unwrap().text()) {
|
w.server_entry.remove_css_class("error");
|
||||||
topic_entry.add_css_class("error");
|
w.topic_entry.remove_css_class("error");
|
||||||
sub_btn.set_sensitive(false);
|
w.sub_btn.set_sensitive(true);
|
||||||
} else {
|
|
||||||
topic_entry.remove_css_class("error");
|
if let Err(errs) = sub {
|
||||||
sub_btn.set_sensitive(true);
|
w.sub_btn.set_sensitive(false);
|
||||||
|
for e in errs {
|
||||||
|
match e {
|
||||||
|
ntfy_daemon::Error::InvalidTopic(_) => {
|
||||||
|
w.topic_entry.add_css_class("error");
|
||||||
|
}
|
||||||
|
ntfy_daemon::Error::InvalidServer(_) => {
|
||||||
|
w.server_entry.add_css_class("error");
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn emit_subscribe_request(&self) {
|
fn emit_subscribe_request(&self) {
|
||||||
|
|||||||
@ -119,7 +119,15 @@ mod imp {
|
|||||||
let this = self.obj().clone();
|
let this = self.obj().clone();
|
||||||
let dc = dialog.clone();
|
let dc = dialog.clone();
|
||||||
dialog.connect_local("subscribe-request", true, move |_| {
|
dialog.connect_local("subscribe-request", true, move |_| {
|
||||||
this.add_subscription(&dc.server(), &dc.topic());
|
let sub = match dc.subscription() {
|
||||||
|
Ok(sub) => sub,
|
||||||
|
Err(e) => {
|
||||||
|
warn!(errors = ?e, "trying to add invalid subscription");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.add_subscription(sub);
|
||||||
|
dc.close();
|
||||||
None
|
None
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -246,11 +254,11 @@ impl NotifyWindow {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_subscription(&self, server: &str, topic: &str) {
|
fn add_subscription(&self, sub: models::Subscription) {
|
||||||
let mut req = self.notifier().subscribe_request();
|
let mut req = self.notifier().subscribe_request();
|
||||||
|
|
||||||
req.get().set_server(server);
|
req.get().set_server(&sub.server);
|
||||||
req.get().set_topic(topic);
|
req.get().set_topic(&sub.topic);
|
||||||
let res = req.send();
|
let res = req.send();
|
||||||
let this = self.clone();
|
let this = self.clone();
|
||||||
self.spawn_with_near_toast(async move {
|
self.spawn_with_near_toast(async move {
|
||||||
|
|||||||
Reference in New Issue
Block a user