diff --git a/Cargo.lock b/Cargo.lock index 128f43a..bf79759 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -842,7 +842,7 @@ checksum = "2a76fa97167fa740dcdbfe18e8895601e1bc36525f09b044e00916e717c03a3c" dependencies = [ "dconf_rs", "detect-desktop-environment", - "dirs", + "dirs 4.0.0", "objc", "rust-ini", "web-sys", @@ -933,6 +933,15 @@ dependencies = [ "dirs-sys 0.3.7", ] +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys 0.5.0", +] + [[package]] name = "dirs-sys" version = "0.3.7" @@ -940,7 +949,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" dependencies = [ "libc", - "redox_users", + "redox_users 0.4.6", "winapi", ] @@ -952,10 +961,22 @@ checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" dependencies = [ "libc", "option-ext", - "redox_users", + "redox_users 0.4.6", "windows-sys 0.48.0", ] +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.5.0", + "windows-sys 0.59.0", +] + [[package]] name = "dispatch" version = "0.2.0" @@ -2334,6 +2355,7 @@ name = "macaw" version = "0.1.0" dependencies = [ "confy", + "dirs 6.0.0", "iced", "indexmap", "jid", @@ -3458,6 +3480,17 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "redox_users" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +dependencies = [ + "getrandom 0.2.15", + "libredox", + "thiserror 2.0.11", +] + [[package]] name = "renderdoc-sys" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index 3d1dcbe..50bbf16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,3 +18,4 @@ indexmap = "2.7.1" serde = { version = "1.0.218", features = ["derive"] } thiserror = "2.0.11" toml = "0.8" +dirs = "6.0.0" diff --git a/src/login_modal.rs b/src/login_modal.rs index 02d878e..5a63158 100644 --- a/src/login_modal.rs +++ b/src/login_modal.rs @@ -34,16 +34,16 @@ pub enum Message { #[derive(Debug, Clone)] pub enum Error { - InvalidJID(String), - DatabaseConnection, + InvalidJID, } pub enum Action { None, ClientCreated(Task), + CreateClient(String, String, bool), } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone)] pub struct Creds { pub jid: String, pub password: String, @@ -69,85 +69,7 @@ impl LoginModal { let jid_str = self.jid.clone(); let password = self.password.clone(); let remember_me = self.remember_me.clone(); - Action::ClientCreated( - Task::future(async move { - let jid: Result = jid_str.parse(); - match jid { - Ok(j) => { - let result = - LuzHandle::new(j.clone(), password.to_string(), "macaw.db") - .await; - match result { - Ok((luz_handle, receiver)) => { - let mut tasks = Vec::new(); - tasks.push(Task::done(crate::Message::ClientCreated( - Client { - client: luz_handle, - jid: j, - connection_status: Presence::Offline( - Offline::default(), - ), - }, - ))); - let stream = ReceiverStream::new(receiver); - let stream = - stream.map(|message| crate::Message::Luz(message)); - tasks.push(Task::stream(stream)); - - if remember_me { - let entry = Entry::new("macaw", "macaw"); - match entry { - Ok(e) => { - let creds = Creds { - jid: jid_str, - password, - }; - let creds = toml::to_string(&creds); - match creds { - Ok(c) => { - let result = e.set_password(&c); - if let Err(e) = result { - tasks.push(Task::done(crate::Message::Error( - crate::Error::CredentialsSave(e.into()), - ))); - } - } - Err(e) => tasks.push(Task::done( - crate::Message::Error( - crate::Error::CredentialsSave( - e.into(), - ), - ), - )), - } - } - Err(e) => { - tasks.push(Task::done(crate::Message::Error( - crate::Error::CredentialsSave(e.into()), - ))) - } - } - } - tasks - } - Err(_e) => { - tracing::error!("error (database probably)"); - return vec![Task::done(crate::Message::LoginModal( - Message::Error(Error::DatabaseConnection), - ))]; - } - } - } - Err(_) => { - tracing::error!("parsing jid"); - return vec![Task::done(crate::Message::LoginModal( - Message::Error(Error::InvalidJID(jid_str.to_string())), - ))]; - } - } - }) - .then(|tasks| Task::batch(tasks)), - ) + Action::CreateClient(jid_str, password, remember_me) } Message::Error(error) => { self.error = Some(error); diff --git a/src/main.rs b/src/main.rs index 558c608..27314ae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,8 @@ use std::borrow::Cow; use std::collections::{HashMap, HashSet}; use std::fmt::Debug; use std::ops::{Deref, DerefMut}; +use std::path::PathBuf; +use std::str::FromStr; use std::sync::Arc; use iced::futures::{SinkExt, Stream, StreamExt}; @@ -30,14 +32,20 @@ use uuid::Uuid; mod login_modal; -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone)] pub struct Config { auto_connect: bool, + storage_dir: Option, + dburl: Option, } impl Default for Config { fn default() -> Self { - Self { auto_connect: true } + Self { + auto_connect: true, + storage_dir: None, + dburl: None, + } } } @@ -109,6 +117,42 @@ impl Deref for Client { } } +async fn luz(jid: &JID, creds: &Creds, cfg: &Config) -> (LuzHandle, mpsc::Receiver) { + let luz; + if let Some(ref dburl) = cfg.dburl { + // TODO: have some sort of crash popup for this stuff + let db_path = dburl.strip_prefix("sqlite://").unwrap_or(&dburl); + let db_path = PathBuf::from_str(db_path).expect("invalid database path"); + let db = luz::db::Db::create_connect_and_migrate(db_path) + .await + .unwrap(); + luz = LuzHandle::new(jid.clone(), creds.password.to_string(), db); + } else if let Some(ref dir) = cfg.storage_dir { + let mut data_dir = PathBuf::from_str(&dir).expect("invalid storage directory path"); + data_dir.push(creds.jid.clone()); + data_dir.push(creds.jid.clone()); + data_dir.set_extension("db"); + let db = luz::db::Db::create_connect_and_migrate(data_dir) + .await + .unwrap(); + luz = LuzHandle::new(jid.clone(), creds.password.to_string(), db); + } else { + let mut data_dir = dirs::data_dir() + .expect("operating system does not support retreiving determining default data dir"); + data_dir.push("macaw"); + data_dir.push(creds.jid.clone()); + data_dir.push(creds.jid.clone()); + // TODO: better lol + data_dir.set_extension("db"); + info!("db_path: {:?}", data_dir); + let db = luz::db::Db::create_connect_and_migrate(data_dir) + .await + .unwrap(); + luz = LuzHandle::new(jid.clone(), creds.password.to_string(), db); + } + luz +} + #[tokio::main] async fn main() -> iced::Result { tracing_subscriber::fmt::init(); @@ -154,11 +198,8 @@ async fn main() -> iced::Result { let jid = creds.jid.parse::(); match jid { Ok(jid) => { - let luz = LuzHandle::new(jid.clone(), creds.password.to_string(), "macaw.db").await; - match luz { - Ok((handle, recv)) => client = Some((jid.as_bare(), handle, recv)), - Err(e) => client_creation_error = Some(Error::ClientCreation(e)), - } + let (handle, updates) = luz(&jid, &creds, &cfg).await; + client = Some((jid, handle, updates)); } Err(e) => client_creation_error = Some(Error::CredentialsLoad(e.into())), } @@ -168,10 +209,43 @@ async fn main() -> iced::Result { let stream = ReceiverStream::new(update_recv); let stream = stream.map(|message| Message::Luz(message)); let task = { + let luz_handle1 = luz_handle.clone(); + let luz_handle2 = luz_handle.clone(); if cfg.auto_connect { - Task::batch([Task::stream(stream), Task::done(Message::Connect)]) + Task::batch([ + Task::perform(async move { luz_handle1.get_roster().await }, |result| { + let roster = result.unwrap(); + let mut macaw_roster = HashMap::new(); + for contact in roster { + macaw_roster.insert(contact.user_jid.clone(), contact); + } + Message::Roster(macaw_roster) + }), + Task::perform(async move { luz_handle2.get_chats().await }, |chats| { + let chats = chats.unwrap(); + info!("got chats: {:?}", chats); + Message::GotChats(chats) + }), + Task::stream(stream), + Task::done(Message::Connect), + ]) } else { - Task::stream(stream) + Task::batch([ + Task::perform(async move { luz_handle1.get_roster().await }, |result| { + let roster = result.unwrap(); + let mut macaw_roster = HashMap::new(); + for contact in roster { + macaw_roster.insert(contact.user_jid.clone(), contact); + } + Message::Roster(macaw_roster) + }), + Task::perform(async move { luz_handle2.get_chats().await }, |chats| { + let chats = chats.unwrap(); + info!("got chats: {:?}", chats); + Message::GotChats(chats) + }), + Task::stream(stream), + ]) } }; iced::application("Macaw", Macaw::update, Macaw::view).run_with(|| { @@ -185,7 +259,6 @@ async fn main() -> iced::Result { }), cfg, ), - // TODO: autoconnect config task, ) }) @@ -345,6 +418,7 @@ impl Macaw { // .into_iter() // .map(|chat| (chat.correspondent.clone(), (chat, IndexMap::new()))) // .collect(); + info!("got chats: {:?}", chats); Message::GotChats(chats) }), Task::done(Message::Connect), @@ -365,6 +439,7 @@ impl Macaw { // .into_iter() // .map(|chat| (chat.correspondent.clone(), (chat, IndexMap::new()))) // .collect(); + info!("got chats: {:?}", chats); Message::GotChats(chats) }), ]) @@ -409,6 +484,71 @@ impl Macaw { let action = login_modal.update(login_modal_message); match action { login_modal::Action::None => Task::none(), + login_modal::Action::CreateClient(jid, password, remember_me) => { + let creds = Creds { jid, password }; + let jid = creds.jid.parse::(); + let config = self.config.clone(); + match jid { + Ok(jid) => { + Task::perform(async move { + let (jid, creds, config) = (jid, creds, config); + let (handle, recv) = luz(&jid, &creds, &config).await; + (handle, recv, jid, creds, config) + }, move |(handle, recv, jid, creds, config)| { + let creds = creds; + let mut tasks = Vec::new(); + tasks.push(Task::done(crate::Message::ClientCreated( + Client { + client: handle, + jid, + connection_status: Presence::Offline( + Offline::default(), + ), + }, + ))); + let stream = ReceiverStream::new(recv); + let stream = + stream.map(|message| crate::Message::Luz(message)); + tasks.push(Task::stream(stream)); + + if remember_me { + let entry = Entry::new("macaw", "macaw"); + match entry { + Ok(e) => { + let creds = toml::to_string(&creds); + match creds { + Ok(c) => { + let result = e.set_password(&c); + if let Err(e) = result { + tasks.push(Task::done(crate::Message::Error( + crate::Error::CredentialsSave(e.into()), + ))); + } + } + Err(e) => tasks.push(Task::done( + crate::Message::Error( + crate::Error::CredentialsSave( + e.into(), + ), + ), + )), + } + } + Err(e) => { + tasks.push(Task::done(crate::Message::Error( + crate::Error::CredentialsSave(e.into()), + ))) + } + } + } + tasks + }).then(|tasks| Task::batch(tasks)) + } + Err(e) => Task::done(Message::LoginModal( + login_modal::Message::Error(login_modal::Error::InvalidJID), + )), + } + } login_modal::Action::ClientCreated(task) => task, } }