feat: store user data in correct dir, or according to config

This commit is contained in:
cel 🌸 2025-03-01 09:11:52 +00:00
parent 18e907386d
commit d0e1226559
4 changed files with 191 additions and 95 deletions

39
Cargo.lock generated
View File

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

View File

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

View File

@ -34,16 +34,16 @@ pub enum Message {
#[derive(Debug, Clone)]
pub enum Error {
InvalidJID(String),
DatabaseConnection,
InvalidJID,
}
pub enum Action {
None,
ClientCreated(Task<crate::Message>),
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, _> = 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);

View File

@ -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<String>,
dburl: Option<String>,
}
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<UpdateMessage>) {
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::<JID>();
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::<JID>();
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,
}
}