From bf677e1f9ce07e2fa8971c15b9a082cddbb40dec Mon Sep 17 00:00:00 2001
From: cel 🌸 <cel@bunny.garden>
Date: Wed, 26 Mar 2025 15:29:11 +0000
Subject: refactor(filament): split logic into different files

---
 filamento/src/logic/process_stanza.rs | 264 ++++++++++++++++++++++++++++++++++
 1 file changed, 264 insertions(+)
 create mode 100644 filamento/src/logic/process_stanza.rs

(limited to 'filamento/src/logic/process_stanza.rs')

diff --git a/filamento/src/logic/process_stanza.rs b/filamento/src/logic/process_stanza.rs
new file mode 100644
index 0000000..17738df
--- /dev/null
+++ b/filamento/src/logic/process_stanza.rs
@@ -0,0 +1,264 @@
+use std::str::FromStr;
+
+use chrono::Utc;
+use lampada::{Connected, SupervisorSender};
+use stanza::client::Stanza;
+use uuid::Uuid;
+
+use crate::{
+    UpdateMessage,
+    chat::{Body, Message},
+    error::{Error, IqError, MessageRecvError, PresenceError, RosterError},
+    presence::{Offline, Online, Presence, PresenceType, Show},
+    roster::Contact,
+};
+
+use super::ClientLogic;
+
+pub async fn handle_stanza(
+    logic: ClientLogic,
+    stanza: Stanza,
+    connection: Connected,
+    supervisor: SupervisorSender,
+) {
+    match stanza {
+        Stanza::Message(stanza_message) => {
+            if let Some(mut from) = stanza_message.from {
+                // TODO: don't ignore delay from. xep says SHOULD send error if incorrect.
+                let timestamp = stanza_message
+                    .delay
+                    .map(|delay| delay.stamp)
+                    .unwrap_or_else(|| Utc::now());
+                // TODO: group chat messages
+                let mut message = Message {
+                    id: stanza_message
+                        .id
+                        // TODO: proper id storage
+                        .map(|id| Uuid::from_str(&id).unwrap_or_else(|_| Uuid::new_v4()))
+                        .unwrap_or_else(|| Uuid::new_v4()),
+                    from: from.clone(),
+                    timestamp,
+                    body: Body {
+                        // TODO: should this be an option?
+                        body: stanza_message
+                            .body
+                            .map(|body| body.body)
+                            .unwrap_or_default()
+                            .unwrap_or_default(),
+                    },
+                };
+                // TODO: can this be more efficient?
+                let result = logic
+                    .db()
+                    .create_message_with_user_resource_and_chat(message.clone(), from.clone())
+                    .await;
+                if let Err(e) = result {
+                    tracing::error!("messagecreate");
+                    let _ = logic
+                        .update_sender()
+                        .send(UpdateMessage::Error(Error::MessageRecv(
+                            MessageRecvError::MessageHistory(e.into()),
+                        )))
+                        .await;
+                }
+                message.from = message.from.as_bare();
+                from = from.as_bare();
+                let _ = logic
+                    .update_sender()
+                    .send(UpdateMessage::Message { to: from, message })
+                    .await;
+            } else {
+                let _ = logic
+                    .update_sender()
+                    .send(UpdateMessage::Error(Error::MessageRecv(
+                        MessageRecvError::MissingFrom,
+                    )))
+                    .await;
+            }
+        }
+        Stanza::Presence(presence) => {
+            if let Some(from) = presence.from {
+                match presence.r#type {
+                    Some(r#type) => match r#type {
+                        // error processing a presence from somebody
+                        stanza::client::presence::PresenceType::Error => {
+                            // TODO: is there any other information that should go with the error? also MUST have an error, otherwise it's a different error. maybe it shoulnd't be an option.
+                            let _ = logic
+                                .update_sender()
+                                .send(UpdateMessage::Error(Error::Presence(
+                                    // TODO: ughhhhhhhhhhhhh these stanza errors should probably just have an option, and custom display
+                                    PresenceError::StanzaError(
+                                        presence
+                                            .errors
+                                            .first()
+                                            .cloned()
+                                            .expect("error MUST have error"),
+                                    ),
+                                )))
+                                .await;
+                        }
+                        // should not happen (error to server)
+                        stanza::client::presence::PresenceType::Probe => {
+                            // TODO: should probably write an error and restart stream
+                            let _ = logic
+                                .update_sender()
+                                .send(UpdateMessage::Error(Error::Presence(
+                                    PresenceError::Unsupported,
+                                )))
+                                .await;
+                        }
+                        stanza::client::presence::PresenceType::Subscribe => {
+                            // may get a subscription request from somebody who is not a contact!!! therefore should be its own kind of event
+                            let _ = logic
+                                .update_sender()
+                                .send(UpdateMessage::SubscriptionRequest(from))
+                                .await;
+                        }
+                        stanza::client::presence::PresenceType::Unavailable => {
+                            let offline = Offline {
+                                status: presence.status.map(|status| status.status.0),
+                            };
+                            let timestamp = presence
+                                .delay
+                                .map(|delay| delay.stamp)
+                                .unwrap_or_else(|| Utc::now());
+                            let _ = logic
+                                .update_sender()
+                                .send(UpdateMessage::Presence {
+                                    from,
+                                    presence: Presence {
+                                        timestamp,
+                                        presence: PresenceType::Offline(offline),
+                                    },
+                                })
+                                .await;
+                        }
+                        // for now, do nothing, as these are simply informational. will receive roster push from the server regarding the changes to do with them.
+                        stanza::client::presence::PresenceType::Subscribed => {}
+                        stanza::client::presence::PresenceType::Unsubscribe => {}
+                        stanza::client::presence::PresenceType::Unsubscribed => {}
+                    },
+                    None => {
+                        let online = Online {
+                            show: presence.show.map(|show| match show {
+                                stanza::client::presence::Show::Away => Show::Away,
+                                stanza::client::presence::Show::Chat => Show::Chat,
+                                stanza::client::presence::Show::Dnd => Show::DoNotDisturb,
+                                stanza::client::presence::Show::Xa => Show::ExtendedAway,
+                            }),
+                            status: presence.status.map(|status| status.status.0),
+                            priority: presence.priority.map(|priority| priority.0),
+                        };
+                        let timestamp = presence
+                            .delay
+                            .map(|delay| delay.stamp)
+                            .unwrap_or_else(|| Utc::now());
+                        let _ = logic
+                            .update_sender()
+                            .send(UpdateMessage::Presence {
+                                from,
+                                presence: Presence {
+                                    timestamp,
+                                    presence: PresenceType::Online(online),
+                                },
+                            })
+                            .await;
+                    }
+                }
+            } else {
+                let _ = logic
+                    .update_sender()
+                    .send(UpdateMessage::Error(Error::Presence(
+                        PresenceError::MissingFrom,
+                    )))
+                    .await;
+            }
+        }
+        Stanza::Iq(iq) => match iq.r#type {
+            stanza::client::iq::IqType::Error | stanza::client::iq::IqType::Result => {
+                let send;
+                {
+                    send = logic.pending().lock().await.remove(&iq.id);
+                }
+                if let Some(send) = send {
+                    send.send(Ok(Stanza::Iq(iq)));
+                } else {
+                    let _ = logic
+                        .update_sender()
+                        .send(UpdateMessage::Error(Error::Iq(IqError::NoMatchingId(
+                            iq.id,
+                        ))))
+                        .await;
+                }
+            }
+            // TODO: send unsupported to server
+            // TODO: proper errors i am so tired please
+            stanza::client::iq::IqType::Get => {}
+            stanza::client::iq::IqType::Set => {
+                if let Some(query) = iq.query {
+                    match query {
+                        stanza::client::iq::Query::Roster(mut query) => {
+                            // TODO: there should only be one
+                            if let Some(item) = query.items.pop() {
+                                match item.subscription {
+                                    Some(stanza::roster::Subscription::Remove) => {
+                                        logic.db().delete_contact(item.jid.clone()).await;
+                                        logic
+                                            .update_sender()
+                                            .send(UpdateMessage::RosterDelete(item.jid))
+                                            .await;
+                                        // TODO: send result
+                                    }
+                                    _ => {
+                                        let contact: Contact = item.into();
+                                        if let Err(e) =
+                                            logic.db().upsert_contact(contact.clone()).await
+                                        {
+                                            let _ = logic
+                                                .update_sender()
+                                                .send(UpdateMessage::Error(Error::Roster(
+                                                    RosterError::Cache(e.into()),
+                                                )))
+                                                .await;
+                                        }
+                                        let _ = logic
+                                            .update_sender()
+                                            .send(UpdateMessage::RosterUpdate(contact))
+                                            .await;
+                                        // TODO: send result
+                                        // write_handle.write(Stanza::Iq(stanza::client::iq::Iq {
+                                        //     from: ,
+                                        //     id: todo!(),
+                                        //     to: todo!(),
+                                        //     r#type: todo!(),
+                                        //     lang: todo!(),
+                                        //     query: todo!(),
+                                        //     errors: todo!(),
+                                        // }));
+                                    }
+                                }
+                            }
+                        }
+                        // TODO: send unsupported to server
+                        _ => {}
+                    }
+                } else {
+                    // TODO: send error (unsupported) to server
+                }
+            }
+        },
+        Stanza::Error(error) => {
+            let _ = logic
+                .update_sender()
+                .send(UpdateMessage::Error(Error::Stream(error)))
+                .await;
+            // TODO: reconnect
+        }
+        Stanza::OtherContent(content) => {
+            let _ = logic
+                .update_sender()
+                .send(UpdateMessage::Error(Error::UnrecognizedContent));
+            // TODO: send error to write_thread
+        }
+    }
+}
-- 
cgit