Save/restore password account

This commit is contained in:
xmppftw xmppftw 2024-06-04 21:57:34 +02:00
parent fe09a79d2c
commit a05e33f4a4
9 changed files with 145 additions and 27 deletions

View File

@ -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" ] }

83
src/config.rs Normal file
View File

@ -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<AccountConfig>,
}
#[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;
}
}

View File

@ -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<RwLock<ConfigFile>>) {
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", &["<Ctrl>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());

View File

@ -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::<Message>();

View File

@ -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));
}

View File

@ -25,7 +25,6 @@ pub struct MainWindow {
pub stack: TemplateChild<gtk::Stack>,
// For the login page
#[template_child]
pub jid: TemplateChild<adw::EntryRow>,
#[template_child]
@ -34,12 +33,10 @@ pub struct MainWindow {
pub login: TemplateChild<gtk::Button>,
// For the spinner
#[template_child]
pub spinner: TemplateChild<gtk::Box>,
// For the chats page
#[template_child]
pub tabs_store: TemplateChild<gio::ListStore>,
#[template_child]

View File

@ -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<imp::MainWindow>)
@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<RwLock<ConfigFile>>) -> 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) -> &gtk::Stack {

26
src/xdg.rs Normal file
View File

@ -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)
}

View File

@ -24,9 +24,7 @@ pub fn tokio_runtime() -> &'static Runtime {
pub async fn handle_xmpp_event(event: xmpp::Event) -> Option<XMPPEvent> {
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