From 365244bd60930e8c68d6933ab13e4b18800a131a Mon Sep 17 00:00:00 2001 From: ranfdev Date: Mon, 13 Nov 2023 19:58:26 +0100 Subject: [PATCH] Add interface to manage accounts --- Cargo.lock | 248 ++++++++++++++++++++++++- data/resources/meson.build | 1 + data/resources/resources.gresource.xml | 1 + data/resources/ui/preferences.blp | 35 ++++ ntfy-daemon/Cargo.toml | 1 + ntfy-daemon/src/lib.rs | 2 + ntfy-daemon/src/ntfy.capnp | 7 + ntfy-daemon/src/system_client.rs | 95 +++++++++- src/application.rs | 14 ++ src/widgets/mod.rs | 2 + src/widgets/preferences.rs | 179 ++++++++++++++++++ 11 files changed, 581 insertions(+), 4 deletions(-) create mode 100644 data/resources/ui/preferences.blp create mode 100644 src/widgets/preferences.rs diff --git a/Cargo.lock b/Cargo.lock index d68240e..8a6c7a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,18 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aes" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", + "zeroize", +] + [[package]] name = "ahash" version = "0.8.6" @@ -415,6 +427,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + [[package]] name = "blocking" version = "1.4.1" @@ -519,6 +540,15 @@ dependencies = [ "capnp 0.17.2", ] +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + [[package]] name = "cc" version = "1.0.83" @@ -558,6 +588,17 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + [[package]] name = "clap" version = "4.4.7" @@ -663,6 +704,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core", "typenum", ] @@ -685,6 +727,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -1343,6 +1386,24 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "http" version = "0.2.9" @@ -1481,6 +1542,16 @@ dependencies = [ "hashbrown 0.14.2", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "block-padding", + "generic-array", +] + [[package]] name = "instant" version = "0.1.12" @@ -1536,6 +1607,9 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] [[package]] name = "libadwaita" @@ -1575,6 +1649,12 @@ version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + [[package]] name = "libsqlite3-sys" version = "0.26.0" @@ -1755,6 +1835,7 @@ dependencies = [ "clap", "futures", "generational-arena", + "oo7", "rand", "regex", "reqwest", @@ -1779,6 +1860,91 @@ dependencies = [ "winapi", ] +[[package]] +name = "num" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "serde", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-complex" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.17" @@ -1832,6 +1998,33 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "oo7" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "220729ba847d98e1a9902c05e41dae79ce4a0b913dad68bc540dd3120a8c2b6b" +dependencies = [ + "aes", + "async-global-executor", + "async-std", + "byteorder", + "cbc", + "cipher", + "digest", + "futures-util", + "hkdf", + "hmac", + "num", + "num-bigint-dig", + "once_cell", + "pbkdf2", + "rand", + "serde", + "sha2", + "zbus", + "zeroize", +] + [[package]] name = "openssl" version = "0.10.59" @@ -1946,6 +2139,16 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + [[package]] name = "percent-encoding" version = "2.3.0" @@ -2211,7 +2414,7 @@ dependencies = [ "cc", "getrandom", "libc", - "spin", + "spin 0.9.8", "untrusted", "windows-sys", ] @@ -2437,6 +2640,17 @@ dependencies = [ "digest", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -2525,6 +2739,12 @@ dependencies = [ "system-deps", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "spin" version = "0.9.8" @@ -2543,6 +2763,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "syn" version = "1.0.109" @@ -3303,6 +3529,26 @@ dependencies = [ "syn 2.0.39", ] +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "zvariant" version = "3.15.0" diff --git a/data/resources/meson.build b/data/resources/meson.build index ae7d9b6..4e54a09 100644 --- a/data/resources/meson.build +++ b/data/resources/meson.build @@ -5,6 +5,7 @@ blueprints = custom_target('blueprints', 'ui/window.blp', 'ui/shortcuts.blp', 'ui/subscription_info_dialog.blp', + 'ui/preferences.blp', ), output: '.', command: [find_program('blueprint-compiler'), 'batch-compile', '@OUTPUT@', '@CURRENT_SOURCE_DIR@', '@INPUT@'], diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml index 18ad92c..4ec9c71 100644 --- a/data/resources/resources.gresource.xml +++ b/data/resources/resources.gresource.xml @@ -5,6 +5,7 @@ ui/shortcuts.ui ui/window.ui ui/subscription_info_dialog.ui + ui/preferences.ui style.css com.ranfdev.Notify.metainfo.xml diff --git a/data/resources/ui/preferences.blp b/data/resources/ui/preferences.blp new file mode 100644 index 0000000..160d058 --- /dev/null +++ b/data/resources/ui/preferences.blp @@ -0,0 +1,35 @@ +using Gtk 4.0; +using Adw 1; + +template $NotifyPreferences : Adw.PreferencesWindow { + width-request: 240; + height-request: 360; + Adw.PreferencesPage { + title: "Accounts"; + description: "Accounts to access protected topics"; + Adw.PreferencesGroup { + title: "New Account"; + Adw.EntryRow server_entry { + title: "server"; + } + Adw.EntryRow username_entry { + title: "username"; + } + Adw.PasswordEntryRow password_entry { + title: "password"; + } + Gtk.Button add_btn { + margin-top: 8; + styles ["suggested-action"] + halign: end; + label: "Add"; + } + } + Adw.PreferencesGroup added_accounts_group { + title: "Added"; + Gtk.ListBox added_accounts { + styles ["boxed-list"] + } + } + } +} diff --git a/ntfy-daemon/Cargo.toml b/ntfy-daemon/Cargo.toml index 7d0be03..08effbd 100644 --- a/ntfy-daemon/Cargo.toml +++ b/ntfy-daemon/Cargo.toml @@ -28,3 +28,4 @@ generational-arena = "0.2.9" tracing = "0.1.37" thiserror = "1.0.49" regex = "1.9.6" +oo7 = "0.2.1" diff --git a/ntfy-daemon/src/lib.rs b/ntfy-daemon/src/lib.rs index f85d8ca..5e7f1f8 100644 --- a/ntfy-daemon/src/lib.rs +++ b/ntfy-daemon/src/lib.rs @@ -7,6 +7,7 @@ pub mod ntfy_capnp { include!(concat!(env!("OUT_DIR"), "/src/ntfy_capnp.rs")); } +use std::rc::Rc; use std::sync::Arc; #[derive(Clone)] @@ -15,6 +16,7 @@ pub struct SharedEnv { proxy: Arc, http: reqwest::Client, network: Arc, + keyring: Rc, } #[derive(thiserror::Error, Debug)] diff --git a/ntfy-daemon/src/ntfy.capnp b/ntfy-daemon/src/ntfy.capnp index 3adb9ba..912d903 100644 --- a/ntfy-daemon/src/ntfy.capnp +++ b/ntfy-daemon/src/ntfy.capnp @@ -33,9 +33,16 @@ interface Subscription { clearNotifications @5 (); } +struct Account { + server @0 :Text; + username @1 :Text; +} interface SystemNotifier { subscribe @0 (server: Text, topic: Text) -> (subscription: Subscription); unsubscribe @1 (server: Text, topic: Text); listSubscriptions @2 () -> (list: List(Subscription)); + addAccount @3 (account: Account, password: Text); + removeAccount @4 (account: Account); + listAccounts @5 () -> (list: List(Account)); } diff --git a/ntfy-daemon/src/system_client.rs b/ntfy-daemon/src/system_client.rs index 3041507..b15d260 100644 --- a/ntfy-daemon/src/system_client.rs +++ b/ntfy-daemon/src/system_client.rs @@ -1,4 +1,4 @@ -use std::cell::{Cell, RefCell}; +use std::cell::{Cell, OnceCell, RefCell}; use std::ops::ControlFlow; use std::rc::{Rc, Weak}; use std::sync::Arc; @@ -20,7 +20,7 @@ use crate::SharedEnv; use crate::{ message_repo::Db, models::{self, MinMessage}, - ntfy_capnp::{output_channel, subscription, system_notifier, watch_handle, Status}, + ntfy_capnp::{account, output_channel, subscription, system_notifier, watch_handle, Status}, topic_listener::{build_client, TopicListener}, }; @@ -343,6 +343,7 @@ impl SystemNotifier { dbpath: &str, notification_proxy: Arc, network: Arc, + keyring: oo7::Keyring, ) -> Self { Self { watching: Rc::new(RefCell::new(HashMap::new())), @@ -351,6 +352,7 @@ impl SystemNotifier { proxy: notification_proxy, http: build_client().unwrap(), network, + keyring: Rc::new(keyring), }, } } @@ -450,6 +452,86 @@ impl system_notifier::Server for SystemNotifier { Promise::ok(()) } + fn list_accounts( + &mut self, + _: system_notifier::ListAccountsParams, + mut results: system_notifier::ListAccountsResults, + ) -> capnp::capability::Promise<(), capnp::Error> { + let keyring = self.env.keyring.clone(); + + Promise::from_future(async move { + let attrs = HashMap::from([("type", "password")]); + let values = keyring + .search_items(attrs) + .await + .map_err(|e| capnp::Error::failed(e.to_string()))?; + + let mut list = results.get().init_list(values.len() as u32); + for (i, item) in values.iter().enumerate() { + let attrs = item + .attributes() + .await + .map_err(|e| capnp::Error::failed(e.to_string()))?; + let mut acc = list.reborrow().get(i as u32); + acc.set_username(attrs["username"][..].into()); + acc.set_server(attrs["server"][..].into()); + } + Ok(()) + }) + } + fn add_account( + &mut self, + params: system_notifier::AddAccountParams, + mut results: system_notifier::AddAccountResults, + ) -> capnp::capability::Promise<(), capnp::Error> { + let keyring = self.env.keyring.clone(); + Promise::from_future(async move { + let account = params.get()?.get_account()?; + let username = account.get_username()?.to_str()?; + let server = account.get_server()?.to_str()?; + let password = params.get()?.get_password()?.to_str()?; + + let attrs = HashMap::from([ + ("type", "password"), + ("username", username), + ("server", server), + ]); + keyring + .create_item("Password", attrs, password, true) + .await + .map_err(|e| capnp::Error::failed(e.to_string()))?; + + info!(server = %server, username = %username, "added account"); + + Ok(()) + }) + } + fn remove_account( + &mut self, + params: system_notifier::RemoveAccountParams, + mut results: system_notifier::RemoveAccountResults, + ) -> capnp::capability::Promise<(), capnp::Error> { + let keyring = self.env.keyring.clone(); + Promise::from_future(async move { + let account = params.get()?.get_account()?; + let username = account.get_username()?.to_str()?; + let server = account.get_server()?.to_str()?; + + let attrs = HashMap::from([ + ("type", "password"), + ("username", username), + ("server", server), + ]); + keyring + .delete(attrs) + .await + .map_err(|e| capnp::Error::failed(e.to_string()))?; + + info!(server = %server, username = %username, "removed account"); + + Ok(()) + }) + } } pub fn start( @@ -467,10 +549,17 @@ pub fn start( UnixListener::bind(&socket_path).unwrap() }); + let keyring = rt.block_on(async { + oo7::Keyring::new() + .await + .expect("Failed to start Secret Service") + }); + let dbpath = dbpath.to_owned(); let f = move || { let local = tokio::task::LocalSet::new(); - let mut system_notifier = SystemNotifier::new(&dbpath, notification_proxy, network_proxy); + let mut system_notifier = + SystemNotifier::new(&dbpath, notification_proxy, network_proxy, keyring); local.spawn_local(async move { system_notifier.watch_subscribed().await.unwrap(); let system_client: system_notifier::Client = capnp_rpc::new_client(system_notifier); diff --git a/src/application.rs b/src/application.rs index ef44faa..82d98ab 100644 --- a/src/application.rs +++ b/src/application.rs @@ -133,6 +133,12 @@ impl NotifyApplication { }) .build(); + let action_about = gio::ActionEntry::builder("preferences") + .activate(|app: &Self, _, _| { + app.show_preferences(); + }) + .build(); + let message_action = gio::ActionEntry::builder("message-action") .parameter_type(Some(&glib::VariantTy::STRING)) .activate(|app: &Self, _, params| { @@ -214,6 +220,14 @@ impl NotifyApplication { dialog.present(); } + fn show_preferences(&self) { + let win = crate::widgets::NotifyPreferences::new( + self.main_window().imp().notifier.get().unwrap().clone(), + ); + win.set_transient_for(Some(&self.main_window())); + win.present(); + } + pub fn run(&self) -> glib::ExitCode { info!(app_id = %APP_ID, version = %VERSION, profile = %PROFILE, datadir = %PKGDATADIR, "running"); diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index fbcf4f5..67d1c2a 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -1,10 +1,12 @@ mod add_subscription_dialog; mod advanced_message_dialog; mod message_row; +mod preferences; mod subscription_info_dialog; mod window; pub use add_subscription_dialog::AddSubscriptionDialog; pub use advanced_message_dialog::*; pub use message_row::*; +pub use preferences::*; pub use subscription_info_dialog::SubscriptionInfoDialog; pub use window::*; diff --git a/src/widgets/preferences.rs b/src/widgets/preferences.rs new file mode 100644 index 0000000..16cd64e --- /dev/null +++ b/src/widgets/preferences.rs @@ -0,0 +1,179 @@ +use std::cell::Cell; +use std::cell::OnceCell; + +use adw::prelude::*; +use adw::subclass::prelude::*; +use futures::prelude::*; +use gtk::{gio, glib}; +use ntfy_daemon::models; +use ntfy_daemon::ntfy_capnp::{system_notifier, Status}; +use tracing::warn; + +use crate::application::NotifyApplication; +use crate::config::{APP_ID, PROFILE}; +use crate::subscription::Subscription; +use crate::widgets::*; + +mod imp { + use super::*; + + #[derive(gtk::CompositeTemplate)] + #[template(resource = "/com/ranfdev/Notify/ui/preferences.ui")] + pub struct NotifyPreferences { + #[template_child] + pub server_entry: TemplateChild, + #[template_child] + pub username_entry: TemplateChild, + #[template_child] + pub password_entry: TemplateChild, + #[template_child] + pub add_btn: TemplateChild, + #[template_child] + pub added_accounts: TemplateChild, + #[template_child] + pub added_accounts_group: TemplateChild, + pub notifier: OnceCell, + } + + impl Default for NotifyPreferences { + fn default() -> Self { + let this = Self { + server_entry: Default::default(), + username_entry: Default::default(), + password_entry: Default::default(), + add_btn: Default::default(), + added_accounts: Default::default(), + added_accounts_group: Default::default(), + notifier: Default::default(), + }; + + this + } + } + + #[glib::object_subclass] + impl ObjectSubclass for NotifyPreferences { + const NAME: &'static str = "NotifyPreferences"; + type Type = super::NotifyPreferences; + type ParentType = adw::PreferencesWindow; + + fn class_init(klass: &mut Self::Class) { + klass.bind_template(); + } + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } + } + + impl ObjectImpl for NotifyPreferences { + fn dispose(&self) { + self.dispose_template(); + } + } + + impl WidgetImpl for NotifyPreferences {} + impl WindowImpl for NotifyPreferences {} + + impl ApplicationWindowImpl for NotifyPreferences {} + impl AdwWindowImpl for NotifyPreferences {} + impl PreferencesWindowImpl for NotifyPreferences {} +} + +glib::wrapper! { + pub struct NotifyPreferences(ObjectSubclass) + @extends gtk::Widget, gtk::Window, adw::Window, adw::PreferencesWindow, + @implements gio::ActionMap, gio::ActionGroup, gtk::Root; +} + +impl NotifyPreferences { + pub fn new(notifier: system_notifier::Client) -> Self { + let obj: Self = glib::Object::builder().build(); + obj.imp() + .notifier + .set(notifier) + .map_err(|_| "notifier") + .unwrap(); + 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 }); + }); + let this = obj.clone(); + obj.imp() + .added_accounts + .spawn_with_near_toast(async move { this.show_accounts().await }); + obj + } + + pub async fn show_accounts(&self) -> anyhow::Result<()> { + let imp = self.imp(); + let req = imp.notifier.get().unwrap().list_accounts_request(); + let res = req.send().promise.await?; + + let accounts = res.get()?.get_list()?; + + imp.added_accounts_group.set_visible(!accounts.is_empty()); + + imp.added_accounts.remove_all(); + for a in accounts { + let server = a.get_server()?.to_string()?; + let username = a.get_username()?.to_string()?; + + let row = adw::ActionRow::builder() + .title(&server) + .subtitle(&username) + .build(); + row.add_css_class("property"); + row.add_suffix(&{ + let btn = gtk::Button::builder() + .icon_name("user-trash-symbolic") + .build(); + btn.add_css_class("flat"); + let this = self.clone(); + btn.connect_clicked(move |btn| { + 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 + }); + imp.added_accounts.append(&row); + } + Ok(()) + } + pub async fn add_account(&self) -> anyhow::Result<()> { + let imp = self.imp(); + let password = imp.password_entry.text(); + let server = imp.server_entry.text(); + let username = imp.username_entry.text(); + + let mut req = imp.notifier.get().unwrap().add_account_request(); + let mut acc = req.get().get_account()?; + acc.set_username(username[..].into()); + acc.set_server(server[..].into()); + req.get().set_password(password[..].into()); + + let res = req.send().promise.await?; + + self.show_accounts().await?; + + Ok(()) + } + pub async fn remove_account(&self, server: &str, username: &str) -> anyhow::Result<()> { + let mut req = self.imp().notifier.get().unwrap().remove_account_request(); + let mut acc = req.get().get_account()?; + + acc.set_username(username[..].into()); + acc.set_server(server[..].into()); + + req.send().promise.await?; + + self.show_accounts().await?; + + Ok(()) + } +}