WIP: data(base)type
This commit is contained in:
parent
8dcdfe405e
commit
0d9e3d27e9
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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() => {
|
||||
|
|
205
luz/src/lib.rs
205
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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -117,7 +117,9 @@ impl ToString for PresenceType {
|
|||
pub enum Show {
|
||||
Away,
|
||||
Chat,
|
||||
/// do not disturb
|
||||
Dnd,
|
||||
/// extended away
|
||||
Xa,
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue