diff options
| author | 2025-02-14 23:57:59 +0000 | |
|---|---|---|
| committer | 2025-02-14 23:57:59 +0000 | |
| commit | 0d9e3d27e9b81411b4d4c53e1b1a1c29087d66f3 (patch) | |
| tree | 90d6d4db55e281ed2f886c5ad034f28db8dc1b43 | |
| parent | 8dcdfe405ecafea04b301a16580ab703a10645eb (diff) | |
| download | luz-0d9e3d27e9b81411b4d4c53e1b1a1c29087d66f3.tar.gz luz-0d9e3d27e9b81411b4d4c53e1b1a1c29087d66f3.tar.bz2 luz-0d9e3d27e9b81411b4d4c53e1b1a1c29087d66f3.zip | |
WIP: data(base)type
Diffstat (limited to '')
| -rw-r--r-- | luz/Cargo.toml | 2 | ||||
| -rw-r--r-- | luz/migrations/20240113011930_luz.sql | 30 | ||||
| -rw-r--r-- | luz/src/chat.rs | 14 | ||||
| -rw-r--r-- | luz/src/connection/read.rs | 3 | ||||
| -rw-r--r-- | luz/src/lib.rs | 197 | ||||
| -rw-r--r-- | luz/src/presence.rs | 4 | ||||
| -rw-r--r-- | luz/src/roster.rs | 12 | ||||
| -rw-r--r-- | stanza/src/client/presence.rs | 2 | 
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,  } | 
