WIP: data(base)type

This commit is contained in:
cel 🌸 2025-02-14 23:57:59 +00:00
parent 8dcdfe405e
commit 0d9e3d27e9
8 changed files with 153 additions and 119 deletions

View File

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

View File

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

View File

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

View File

@ -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() => {

View File

@ -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()));
}
}
}
};
}
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!()
};
}
},
_ => {
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(),
)),
};
}
}
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;
}
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()));
}
}
}
};
}
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!()
};
}
},
_ => {
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,
},
}

View File

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

View File

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

View File

@ -117,7 +117,9 @@ impl ToString for PresenceType {
pub enum Show {
Away,
Chat,
/// do not disturb
Dnd,
/// extended away
Xa,
}