diff --git a/Cargo.toml b/Cargo.toml index 917872f..801d188 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,3 +18,7 @@ xmpp = { git = "https://gitlab.com/xmpp-rs/xmpp-rs" } tokio-xmpp = { git = "https://gitlab.com/xmpp-rs/xmpp-rs", features = ["syntax-highlighting"] } async-channel = "2.3.1" env_logger = { version = "0.11.3", default-features = false, features = ["color", "auto-color", "humantime"] } +camino = "1.1" +serde = { version = "1.0", features = [ "derive" ] } +toml = "0.8" +jid = { git = "https://gitlab.com/xmpp-rs/xmpp-rs", features = [ "serde" ] } diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..da13fa7 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,83 @@ +use camino::Utf8PathBuf; +use serde::{Deserialize, Serialize}; +use xmpp::BareJid; + +use crate::xdg::xdg_config; + +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct ConfigFile { + pub accounts: Vec, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct AccountConfig { + jid: BareJid, + password: String, +} + +impl AccountConfig { + pub fn new(jid: &BareJid, password: &str) -> Self { + Self { + jid: jid.clone(), + password: password.to_string(), + } + } +} + +impl ConfigFile { + pub fn xdg_path() -> Utf8PathBuf { + xdg_config("rino").join("config.toml") + } + + pub fn from_xdg() -> Self { + let config_path = Self::xdg_path(); + if config_path.is_file() { + let content = std::fs::read_to_string(&config_path).unwrap(); + toml::from_str(&content).unwrap() + } else { + Self::default() + } + } + + pub fn to_xdg(&self) { + let config_path = Self::xdg_path(); + let config_dir = config_path.parent().unwrap(); + if !config_dir.is_dir() { + std::fs::create_dir_all(&config_dir).unwrap(); + } + std::fs::write(&config_path, toml::to_string(&self).unwrap()).unwrap() + } + + pub fn get_account_password(&self, jid: &BareJid) -> Option<&str> { + self.accounts + .iter() + .find(|acc| &acc.jid == jid) + .map(|x| x.password.as_str()) + } + + pub fn save_account(&mut self, jid: &BareJid, password: &str) { + if let Some(p) = self.get_account_password(jid) { + if p == password { + return; + } + + for acc in self.accounts.iter_mut() { + if &acc.jid == jid { + acc.password = password.to_string(); + } + } + } else { + let account = AccountConfig::new(jid, password); + self.accounts.push(account); + } + self.to_xdg(); + } + + pub fn get_first_account(&self) -> Option<(&BareJid, &String)> { + for account in &self.accounts { + return Some((&account.jid, &account.password)); + } + + return None; + } +} diff --git a/src/main.rs b/src/main.rs index f0522e8..55ddc02 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,13 +18,18 @@ use adw::prelude::*; use adw::subclass::prelude::ObjectSubclassIsExt; use gtk::{gio, glib}; +use std::sync::{Arc, RwLock}; + +mod config; mod message; mod poezio_logs; mod tab; mod widgets; mod window; +mod xdg; mod xmpp_client; +use config::ConfigFile; use message::Message; use tab::Tab; @@ -41,7 +46,7 @@ fn xep_0392(input: &str) -> String { format!("#{:02x}{:02x}{:02x}", r, g, b) } -fn on_login_pressed(win: window::MainWindow) { +fn on_login_pressed(win: window::MainWindow, config: Arc>) { let jid = win.jid().text(); let password = win.password().text(); @@ -54,6 +59,11 @@ fn on_login_pressed(win: window::MainWindow) { while let Ok(event) = xmpp_receiver.recv().await { match event { xmpp_client::XMPPEvent::Online => { + // Success login, save password + config + .write() + .unwrap() + .save_account(&jid::BareJid::new(jid.as_str()).unwrap(), &password); win.stack().set_visible_child(win.split_view()); } xmpp_client::XMPPEvent::Avatar(jid, avatar) => { @@ -84,6 +94,8 @@ fn on_login_pressed(win: window::MainWindow) { fn main() { env_logger::init(); + let config = Arc::new(RwLock::new(ConfigFile::from_xdg())); + let app = adw::Application::builder() .application_id("fr.linkmauve.XmppClient") .flags(gio::ApplicationFlags::HANDLES_COMMAND_LINE) @@ -106,7 +118,7 @@ fn main() { }); app.connect_startup(move |app| { - let win = window::MainWindow::new(app); + let win = window::MainWindow::new(app, config.clone()); let action_close = gio::ActionEntry::builder("close") .activate(|window: &window::MainWindow, _, _| { @@ -117,8 +129,9 @@ fn main() { app.set_accels_for_action("win.close", &["Q"]); let win2 = win.clone(); + let config2 = config.clone(); win.login().connect_clicked(move |_| { - on_login_pressed(win2.clone()); + on_login_pressed(win2.clone(), config2.clone()); }); assert!(Tab::static_type().is_valid()); diff --git a/src/poezio_logs.rs b/src/poezio_logs.rs index 5ab7d8d..95f8431 100644 --- a/src/poezio_logs.rs +++ b/src/poezio_logs.rs @@ -25,9 +25,9 @@ use nom::{ }; use std::fs::read_to_string; -use std::path::PathBuf; use std::str::FromStr; +use crate::xdg::xdg_data; use crate::Message; pub trait LogItem { @@ -155,13 +155,7 @@ pub enum Item<'a> { } pub fn load_logs(jid: &str) -> gio::ListStore { - let xdg_data = std::env::var("XDG_DATA_HOME").unwrap_or(format!( - "{}/.local/share", - std::env::var("HOME").expect("NO $HOME?!") - )); - let xdg_data = PathBuf::from(xdg_data); - let entry = xdg_data.join("poezio/logs").join(jid); - + let entry = xdg_data("poezio").join("logs").join(jid); let logs = read_to_string(entry).unwrap_or("".to_string()); let (_, logs) = parse_logs(&logs).unwrap(); let store = gio::ListStore::new::(); diff --git a/src/widgets/chat_tab.rs b/src/widgets/chat_tab.rs index 9e0f7b2..6a3882e 100644 --- a/src/widgets/chat_tab.rs +++ b/src/widgets/chat_tab.rs @@ -71,15 +71,8 @@ impl ChatTab { self.set_tooltip_text(Some(jid)); } + // TODO: currently file path, not a hash pub fn set_avatar_hash(&self, hash: &str) { - // let xdg_cache = std::env::var("XDG_CACHE_HOME").unwrap_or(format!( - // "{}/.cache", - // std::env::var("HOME").expect("NO $HOME?!") - // )); - // let xdg_cache = PathBuf::from(xdg_cache); - // let entry = xdg_cache.join("poezio/avatars").join(self.tooltip_text().unwrap()).join(hash); - - // self.imp().avatar.set_from_file(Some(entry.to_str().unwrap().to_string())); self.imp().avatar.set_from_file(Some(hash)); } diff --git a/src/window/imp.rs b/src/window/imp.rs index fb4a2ba..fb1afb4 100644 --- a/src/window/imp.rs +++ b/src/window/imp.rs @@ -25,7 +25,6 @@ pub struct MainWindow { pub stack: TemplateChild, // For the login page - #[template_child] pub jid: TemplateChild, #[template_child] @@ -34,12 +33,10 @@ pub struct MainWindow { pub login: TemplateChild, // For the spinner - #[template_child] pub spinner: TemplateChild, // For the chats page - #[template_child] pub tabs_store: TemplateChild, #[template_child] diff --git a/src/window/mod.rs b/src/window/mod.rs index 7ac96cb..752ec27 100644 --- a/src/window/mod.rs +++ b/src/window/mod.rs @@ -16,9 +16,14 @@ mod imp; +use adw::prelude::*; use adw::subclass::prelude::*; use gtk::{gio, glib}; +use std::sync::{Arc, RwLock}; + +use crate::config::ConfigFile; + glib::wrapper! { pub struct MainWindow(ObjectSubclass) @extends gtk::Widget, gtk::Window, gtk::ApplicationWindow, adw::ApplicationWindow, @@ -26,8 +31,13 @@ glib::wrapper! { } impl MainWindow { - pub fn new(app: &adw::Application) -> Self { - glib::Object::builder().property("application", app).build() + pub fn new(app: &adw::Application, config: Arc>) -> Self { + let win: Self = glib::Object::builder().property("application", app).build(); + if let Some((jid, password)) = config.read().unwrap().get_first_account() { + win.jid().set_text(jid.as_str()); + win.password().set_text(password); + } + win } pub fn stack(&self) -> >k::Stack { diff --git a/src/xdg.rs b/src/xdg.rs new file mode 100644 index 0000000..018253e --- /dev/null +++ b/src/xdg.rs @@ -0,0 +1,26 @@ +use camino::Utf8PathBuf; + +pub fn xdg_data(app: &str) -> Utf8PathBuf { + let xdg_data = std::env::var("XDG_DATA_HOME").unwrap_or(format!( + "{}/.local/share", + std::env::var("HOME").expect("NO $HOME?!") + )); + Utf8PathBuf::from(xdg_data).join(app) +} + +#[allow(dead_code)] +pub fn xdg_cache(app: &str) -> Utf8PathBuf { + let xdg_cache = std::env::var("XDG_CACHE_HOME").unwrap_or(format!( + "{}/.cache", + std::env::var("HOME").expect("NO $HOME?!") + )); + Utf8PathBuf::from(xdg_cache).join(app) +} + +pub fn xdg_config(app: &str) -> Utf8PathBuf { + let xdg_config = std::env::var("XDG_CONFIG_HOME").unwrap_or(format!( + "{}/.config", + std::env::var("HOME").expect("NO $HOME?!") + )); + Utf8PathBuf::from(xdg_config).join(app) +} diff --git a/src/xmpp_client.rs b/src/xmpp_client.rs index 62fc2cb..92ba903 100644 --- a/src/xmpp_client.rs +++ b/src/xmpp_client.rs @@ -24,9 +24,7 @@ pub fn tokio_runtime() -> &'static Runtime { pub async fn handle_xmpp_event(event: xmpp::Event) -> Option { match event { - xmpp::Event::Online => { - Some(XMPPEvent::Online) - } + xmpp::Event::Online => Some(XMPPEvent::Online), xmpp::Event::AvatarRetrieved(jid, hash) => { // need update latest symlink? // xmpp-rs stores avatar in data/JID/HASH... we create data/JID/latest symlink points to relative HASH