aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar cel 🌸 <cel@blos.sm>2025-02-14 23:57:59 +0000
committerLibravatar cel 🌸 <cel@blos.sm>2025-02-14 23:57:59 +0000
commit0d9e3d27e9b81411b4d4c53e1b1a1c29087d66f3 (patch)
tree90d6d4db55e281ed2f886c5ad034f28db8dc1b43
parent8dcdfe405ecafea04b301a16580ab703a10645eb (diff)
downloadluz-0d9e3d27e9b81411b4d4c53e1b1a1c29087d66f3.tar.gz
luz-0d9e3d27e9b81411b4d4c53e1b1a1c29087d66f3.tar.bz2
luz-0d9e3d27e9b81411b4d4c53e1b1a1c29087d66f3.zip
WIP: data(base)type
-rw-r--r--luz/Cargo.toml2
-rw-r--r--luz/migrations/20240113011930_luz.sql30
-rw-r--r--luz/src/chat.rs14
-rw-r--r--luz/src/connection/read.rs3
-rw-r--r--luz/src/lib.rs197
-rw-r--r--luz/src/presence.rs4
-rw-r--r--luz/src/roster.rs12
-rw-r--r--stanza/src/client/presence.rs2
8 files changed, 149 insertions, 115 deletions
diff --git a/luz/Cargo.toml b/luz/Cargo.toml
index cd86ce2..6417d5d 100644
--- a/luz/Cargo.toml
+++ b/luz/Cargo.toml
@@ -8,7 +8,7 @@ futures = "0.3.31"
jabber = { version = "0.1.0", path = "../jabber" }
peanuts = { version = "0.1.0", path = "../../peanuts" }
jid = { version = "0.1.0", path = "../jid" }
-sqlx = { version = "0.8.3", features = [ "sqlite", "runtime-tokio" ] }
+sqlx = { version = "0.8.3", features = ["sqlite", "runtime-tokio"] }
stanza = { version = "0.1.0", path = "../stanza" }
tokio = "1.42.0"
tokio-stream = "0.1.17"
diff --git a/luz/migrations/20240113011930_luz.sql b/luz/migrations/20240113011930_luz.sql
index d8eb90d..5b6b50b 100644
--- a/luz/migrations/20240113011930_luz.sql
+++ b/luz/migrations/20240113011930_luz.sql
@@ -2,15 +2,15 @@ PRAGMA foreign_keys = on;
-- a user jid will never change, only a chat user will change
-- TODO: avatar, nick, etc.
-create table user(
+create table users(
jid jid primary key,
-- can receive presence status from non-contacts
- cached_status text,
+ cached_status_message text
);
-- enum for subscription state
create table subscription(
- state text primary key,
+ state text primary key
);
insert into subscription ( state ) values ('none'), ('pending-out'), ('pending-in'), ('only-out'), ('only-in'), ('out-pending-in'), ('in-pending-out'), ('buddy');
@@ -30,9 +30,9 @@ create table groups(
create table groups_roster(
group_id text,
- contact_id jid,
+ contact_jid jid,
foreign key(group_id) references group(id),
- foreign key(contact_id) references roster(id),
+ foreign key(contact_jid) references roster(jid),
primary key(group_id, contact_id)
);
@@ -41,17 +41,29 @@ create table groups_roster(
-- can send chat message to user (creating a new chat if not already exists)
create table chats (
id uuid primary key,
- user_id jid not null unique,
+ user_jid jid not null unique,
+ foreign key(user_jid) references users(jid)
);
-- messages include reference to chat they are in, and who sent them.
create table messages (
id uuid primary key,
body text,
- chat_id uuid not null,
+ chat_id uuid,
+ -- TODO: channel stuff
+ -- channel_id uuid,
+ -- check ((chat_id == null) <> (channel_id == null)),
+ -- check ((chat_id == null) or (channel_id == null)),
+ -- user is the current "owner" of the message
+
+ -- TODO: icky
+ from_jid jid not null,
+ originally_from jid not null,
+ check (from_jid != original_sender),
+
-- TODO: from can be either a jid, a moved jid (for when a contact moves, save original sender jid/user but link to new user), or imported (from another service (save details), linked to new user)
- from jid not null,
-- TODO: read bool not null,
foreign key(chat_id) references chats(id),
- foreign key(from) references users(jid)
+ foreign key(from_jid) references users(jid),
+ foreign key(originally_from) references users(jid)
);
diff --git a/luz/src/chat.rs b/luz/src/chat.rs
index 091b3b6..24ad709 100644
--- a/luz/src/chat.rs
+++ b/luz/src/chat.rs
@@ -1,3 +1,4 @@
+use jid::JID;
use uuid::Uuid;
use crate::{roster::Contact, user::User};
@@ -6,22 +7,27 @@ use crate::{roster::Contact, user::User};
pub struct Message {
id: Uuid,
// contains full user information
- from: User,
- // TODO: rich text, other contents, threads
+ from: Correspondent,
body: Body,
}
#[derive(Debug)]
pub struct Body {
+ // TODO: rich text, other contents, threads
body: String,
}
pub struct Chat {
- id: Uuid,
- user: User,
+ correspondent: Correspondent,
message_history: Vec<Message>,
}
+#[derive(Debug)]
+pub enum Correspondent {
+ User(User),
+ Contact(Contact),
+}
+
// TODO: group chats
// pub enum Chat {
// Direct(DirectChat),
diff --git a/luz/src/connection/read.rs b/luz/src/connection/read.rs
index c2828ad..d005693 100644
--- a/luz/src/connection/read.rs
+++ b/luz/src/connection/read.rs
@@ -12,6 +12,7 @@ use tokio::{
sync::{mpsc, oneshot, Mutex},
task::{JoinHandle, JoinSet},
};
+use tracing::info;
use crate::{error::Error, UpdateMessage};
@@ -87,7 +88,7 @@ impl Read {
// if still haven't received the end tag in time, just kill itself
// TODO: is this okay??? what if notification thread dies?
Ok(()) = &mut self.disconnect_timedout => {
- println!("disconnect_timedout");
+ info!("disconnect_timedout");
break;
}
Some(msg) = self.control_receiver.recv() => {
diff --git a/luz/src/lib.rs b/luz/src/lib.rs
index 07dc74a..79df494 100644
--- a/luz/src/lib.rs
+++ b/luz/src/lib.rs
@@ -4,11 +4,11 @@ use std::{
sync::Arc,
};
-use chat::{Body, Message};
+use chat::{Body, Chat, Message};
use connection::{write::WriteMessage, SupervisorSender};
use jabber::JID;
use presence::{Offline, Online, Presence};
-use roster::Contact;
+use roster::{Contact, ContactUpdate};
use sqlx::SqlitePool;
use stanza::client::{
iq::{self, Iq, IqType},
@@ -72,89 +72,89 @@ impl Luz {
async fn run(mut self) {
loop {
- tokio::select! {
+ let msg = tokio::select! {
// this is okay, as when created the supervisor (and connection) doesn't exist, but a bit messy
_ = &mut self.connection_supervisor_shutdown => {
- *self.connected.lock().await = None
+ *self.connected.lock().await = None;
+ continue;
}
Some(msg) = self.receiver.recv() => {
- // TODO: consider separating disconnect/connect and commands apart from commandmessage
- // TODO: dispatch commands separate tasks
- match msg {
- CommandMessage::Connect => {
- let mut connection_lock = self.connected.lock().await;
- match connection_lock.as_ref() {
- Some(_) => {
- self.sender
- .send(UpdateMessage::Error(Error::AlreadyConnected))
- .await;
- }
- None => {
- let mut jid = self.jid.lock().await;
- let mut domain = jid.domainpart.clone();
- // TODO: check what happens upon reconnection with same resource (this is probably what one wants to do and why jid should be mutated from a bare jid to one with a resource)
- let streams_result =
- jabber::connect_and_login(&mut jid, &*self.password, &mut domain)
- .await;
- match streams_result {
- Ok(s) => {
- let (shutdown_send, shutdown_recv) = oneshot::channel::<()>();
- let (writer, supervisor) = SupervisorHandle::new(
- s,
- self.sender.clone(),
- self.db.clone(),
- shutdown_send,
- self.jid.clone(),
- self.password.clone(),
- self.pending_iqs.clone(),
- );
- self.connection_supervisor_shutdown = shutdown_recv;
- *connection_lock = Some((writer, supervisor));
- self.sender
- .send(UpdateMessage::Connected)
- .await;
- }
- Err(e) => {
- self.sender.send(UpdateMessage::Error(e.into()));
- }
- }
- }
- };
+ msg
+ },
+ else => break,
+ };
+ // TODO: consider separating disconnect/connect and commands apart from commandmessage
+ // TODO: dispatch commands separate tasks
+ match msg {
+ CommandMessage::Connect => {
+ let mut connection_lock = self.connected.lock().await;
+ match connection_lock.as_ref() {
+ Some(_) => {
+ self.sender
+ .send(UpdateMessage::Error(Error::AlreadyConnected))
+ .await;
}
- CommandMessage::Disconnect => match self.connected.lock().await.as_mut() {
- None => {
- self.sender
- .send(UpdateMessage::Error(Error::AlreadyDisconnected))
+ None => {
+ let mut jid = self.jid.lock().await;
+ let mut domain = jid.domainpart.clone();
+ // TODO: check what happens upon reconnection with same resource (this is probably what one wants to do and why jid should be mutated from a bare jid to one with a resource)
+ let streams_result =
+ jabber::connect_and_login(&mut jid, &*self.password, &mut domain)
.await;
+ match streams_result {
+ Ok(s) => {
+ let (shutdown_send, shutdown_recv) = oneshot::channel::<()>();
+ let (writer, supervisor) = SupervisorHandle::new(
+ s,
+ self.sender.clone(),
+ self.db.clone(),
+ shutdown_send,
+ self.jid.clone(),
+ self.password.clone(),
+ self.pending_iqs.clone(),
+ );
+ self.connection_supervisor_shutdown = shutdown_recv;
+ *connection_lock = Some((writer, supervisor));
+ self.sender.send(UpdateMessage::Connected(todo!())).await;
+ }
+ Err(e) => {
+ self.sender.send(UpdateMessage::Error(e.into()));
+ }
}
- mut c => {
- if let Some((_write_handle, supervisor_handle)) = c.take() {
- let _ = supervisor_handle.send(SupervisorCommand::Disconnect).await;
- } else {
- unreachable!()
- };
- }
- },
- _ => {
- match self.connected.lock().await.as_ref() {
- Some((w, s)) => self.tasks.spawn(msg.handle_online(
- w.clone(),
- s.sender(),
- self.jid.clone(),
- self.db.clone(),
- self.sender.clone(),
- self.pending_iqs.clone()
- )),
- None => self.tasks.spawn(msg.handle_offline(
- self.jid.clone(),
- self.db.clone(),
- self.sender.clone(),
- )),
- };
}
+ };
+ }
+ CommandMessage::Disconnect => match self.connected.lock().await.as_mut() {
+ None => {
+ self.sender
+ .send(UpdateMessage::Error(Error::AlreadyDisconnected))
+ .await;
+ }
+ mut c => {
+ if let Some((_write_handle, supervisor_handle)) = c.take() {
+ let _ = supervisor_handle.send(SupervisorCommand::Disconnect).await;
+ } else {
+ unreachable!()
+ };
}
},
- else => break,
+ _ => {
+ match self.connected.lock().await.as_ref() {
+ Some((w, s)) => self.tasks.spawn(msg.handle_online(
+ w.clone(),
+ s.sender(),
+ self.jid.clone(),
+ self.db.clone(),
+ self.sender.clone(),
+ self.pending_iqs.clone(),
+ )),
+ None => self.tasks.spawn(msg.handle_offline(
+ self.jid.clone(),
+ self.db.clone(),
+ self.sender.clone(),
+ )),
+ };
+ }
}
}
}
@@ -213,7 +213,8 @@ impl CommandMessage {
e => println!("error: {:?}", e),
};
}
- CommandMessage::SendMessage(jid, _) => todo!(),
+ CommandMessage::SendMessage { id, to, body } => todo!(),
+ _ => todo!(),
}
}
}
@@ -274,10 +275,16 @@ pub enum CommandMessage {
/// connect to XMPP chat server. gets roster and publishes initial presence.
Connect,
/// disconnect from XMPP chat server, sending unavailable presence then closing stream.
- Disconnect,
+ Disconnect(Offline),
/// get the roster. if offline, retreive cached version from database. should be stored in application memory
GetRoster,
- // add a contact to your roster, with a status of none, no subscriptions
+ /// get all chats. chat will include 10 messages in their message Vec (enough for chat previews)
+ // TODO: paging and filtering
+ GetChats(oneshot::Sender<Vec<Chat>>),
+ /// get message history for chat (does appropriate mam things)
+ // TODO: paging and filtering
+ GetMessages(JID, oneshot::Sender<Vec<Message>>),
+ /// add a contact to your roster, with a status of none, no subscriptions.
AddContact(JID),
/// send a friend request i.e. a subscription request with a subscription pre-approval. if not already added to roster server adds to roster.
BuddyRequest(JID),
@@ -295,38 +302,34 @@ pub enum CommandMessage {
UnfriendContact(JID),
/// remove a contact from the contact list. will remove subscriptions if not already done then delete contact from roster.
DeleteContact(JID),
- /// set online status
- SendStatus(Online),
- SendOffline(Offline),
+ /// update contact
+ UpdateContact(JID, ContactUpdate),
+ /// set online status. if disconnected, will be cached so when client connects, will be sent as the initial presence.
+ SetStatus(Online),
/// send a directed presence (usually to a non-contact).
// TODO: should probably make it so people can add non-contact auto presence sharing in the client.
- SendDirectedPresence {
- to: JID,
- presence: Presence,
- },
- SendMessage {
- id: Uuid,
- to: JID,
- body: Body,
- },
+ // SendDirectedPresence(JID, Online),
+ /// send a message to a jid (any kind of jid that can receive a message, e.g. a user or a
+ /// chatroom). if disconnected, will be cached so when client connects, message will be sent.
+ SendMessage(JID, Body),
}
#[derive(Debug)]
pub enum UpdateMessage {
Error(Error),
- Connected(Online),
- Disconnected(Offline),
- /// full roster (replace full app roster state with this)
- Roster(Vec<Contact>),
- /// roster update (only update app roster state)
- RosterPush(Contact),
+ Online(Online),
+ Offline(Offline),
+ /// received roster (replace full app roster state with this)
+ FullRoster(Vec<Contact>),
+ /// (only update app roster state)
+ RosterUpdate(Contact),
Presence {
from: JID,
presence: Presence,
},
MessageDispatched(Uuid),
Message {
- from: JID,
+ to: JID,
message: Message,
},
}
diff --git a/luz/src/presence.rs b/luz/src/presence.rs
index 0423d52..b7ebe1d 100644
--- a/luz/src/presence.rs
+++ b/luz/src/presence.rs
@@ -1,13 +1,13 @@
use stanza::client::presence::Show;
-#[derive(Debug)]
+#[derive(Debug, Default)]
pub struct Online {
show: Option<Show>,
status: Option<String>,
priority: Option<i8>,
}
-#[derive(Debug)]
+#[derive(Debug, Default)]
pub struct Offline {
status: Option<String>,
}
diff --git a/luz/src/roster.rs b/luz/src/roster.rs
index 421ee9d..512d35d 100644
--- a/luz/src/roster.rs
+++ b/luz/src/roster.rs
@@ -5,11 +5,16 @@ use uuid::Uuid;
use crate::user::User;
+pub enum ContactUpdate {
+ Name(Option<String>),
+ AddToGroup(String),
+ RemoveFromGroup(String),
+}
+
#[derive(Debug)]
pub struct Contact {
// jid is the id used to reference everything, but not the primary key
user: User,
- jid: JID,
subscription: Subscription,
/// client user defined name
name: Option<String>,
@@ -19,6 +24,10 @@ pub struct Contact {
groups: HashSet<String>,
}
+impl Contact {
+ pub fn new(user: User, name: Option<String>, )
+}
+
#[derive(Debug)]
enum Subscription {
None,
@@ -29,4 +38,5 @@ enum Subscription {
OutPendingIn,
InPendingOut,
Buddy,
+ Remove,
}
diff --git a/stanza/src/client/presence.rs b/stanza/src/client/presence.rs
index 5354966..877c4c6 100644
--- a/stanza/src/client/presence.rs
+++ b/stanza/src/client/presence.rs
@@ -117,7 +117,9 @@ impl ToString for PresenceType {
pub enum Show {
Away,
Chat,
+ /// do not disturb
Dnd,
+ /// extended away
Xa,
}