aboutsummaryrefslogtreecommitdiffstats
path: root/filamento/src
diff options
context:
space:
mode:
Diffstat (limited to 'filamento/src')
-rw-r--r--filamento/src/avatar.rs4
-rw-r--r--filamento/src/caps.rs4
-rw-r--r--filamento/src/chat.rs40
-rw-r--r--filamento/src/db.rs339
-rw-r--r--filamento/src/disco.rs4
-rw-r--r--filamento/src/error.rs4
-rw-r--r--filamento/src/files.rs4
-rw-r--r--filamento/src/files/opfs.rs4
-rw-r--r--filamento/src/lib.rs7
-rw-r--r--filamento/src/logic/abort.rs4
-rw-r--r--filamento/src/logic/connect.rs4
-rw-r--r--filamento/src/logic/connection_error.rs4
-rw-r--r--filamento/src/logic/disconnect.rs4
-rw-r--r--filamento/src/logic/local_only.rs4
-rw-r--r--filamento/src/logic/mod.rs4
-rw-r--r--filamento/src/logic/offline.rs15
-rw-r--r--filamento/src/logic/online.rs9
-rw-r--r--filamento/src/logic/process_stanza.rs34
-rw-r--r--filamento/src/pep.rs4
-rw-r--r--filamento/src/presence.rs4
-rw-r--r--filamento/src/roster.rs4
-rw-r--r--filamento/src/user.rs4
22 files changed, 318 insertions, 190 deletions
diff --git a/filamento/src/avatar.rs b/filamento/src/avatar.rs
index a6937df..df30a6a 100644
--- a/filamento/src/avatar.rs
+++ b/filamento/src/avatar.rs
@@ -1,3 +1,7 @@
+// SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
#[derive(Clone, Debug)]
pub struct Metadata {
pub bytes: u32,
diff --git a/filamento/src/caps.rs b/filamento/src/caps.rs
index e0587ff..305c9b7 100644
--- a/filamento/src/caps.rs
+++ b/filamento/src/caps.rs
@@ -1,3 +1,7 @@
+// SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
use std::str::FromStr;
use base64::{Engine, prelude::BASE64_STANDARD};
diff --git a/filamento/src/chat.rs b/filamento/src/chat.rs
index c02654f..687da82 100644
--- a/filamento/src/chat.rs
+++ b/filamento/src/chat.rs
@@ -1,3 +1,7 @@
+// SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
use std::fmt::{Display, Write};
use chrono::{DateTime, Utc};
@@ -13,17 +17,43 @@ use uuid::Uuid;
#[cfg_attr(feature = "reactive_stores", derive(reactive_stores::Store))]
pub struct Message {
pub id: Uuid,
- // does not contain full user information
- // bare jid (for now)
+ /// jid of user currently tied to the original sender, updated by jid move event. original sender can be found within the source data.
pub from: BareJID,
pub delivery: Option<Delivery>,
pub timestamp: DateTime<Utc>,
- // TODO: originally_from
- // TODO: message edits
- // TODO: message timestamp
+ // TODO: message edits. message edits will need to include the edit and the source stanza that caused the edit.
+ /// original message sources (XMPP, imported, etc.). cannot change, but may be deleted or have information redacted. can be multiple because a message may be updated, have reactions appended to it, delivery receipt, user moved, etc.
+ pub source: Vec<Source>,
pub body: Body,
}
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum Source {
+ XMPP(XMPPMessage),
+ // TODO: imported information
+ Imported,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct XMPPMessage {
+ // TODO: upon software updates, upgrade all message and xmppmessage structs with missed data from old XMPP raw messages. maybe just don't store parsed message in xmpp message, have it be a method
+ /// the raw data received from the xml stream
+ pub raw: String,
+ /// the timestamp the client received the stanza, or associated with the <delay> in the envelope.
+ pub timestamp: DateTime<Utc>,
+ /// if message was received in a carbon envolope, forwarded, or in an encrypted envelope, etc., the full xmpp message it came from is included here, linked. there could technically be multiple envelopes (same stanza received through multiple envelopes).
+ pub envelopes: Vec<XMPPMessage>,
+}
+
+impl XMPPMessage {
+ // TODO: syncify
+ // pub async fn parsed(&self) -> stanza::client::message::Message {
+ // let reader = peanuts::Reader::new(peanuts::ReadableString(self.raw.to_string()));
+ // let message = reader.read().await?;
+ // message
+ // }
+}
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum Delivery {
diff --git a/filamento/src/db.rs b/filamento/src/db.rs
index 298d54a..b9858db 100644
--- a/filamento/src/db.rs
+++ b/filamento/src/db.rs
@@ -1,8 +1,15 @@
+// SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
use core::fmt::Display;
+use std::ffi::c_int;
use std::{collections::HashSet, ops::Deref, path::Path, sync::Arc};
use chrono::{DateTime, Utc};
use jid::{BareJID, FullJID, JID};
+use rusqlite::trace::TraceEventCodes;
+use rusqlite::trace::config_log;
use rusqlite::{Connection, OptionalExtension};
use tokio::sync::{Mutex, MutexGuard};
use tokio::sync::{mpsc, oneshot};
@@ -53,6 +60,41 @@ macro_rules! impl_db_sends {
}
}
+unsafe fn enable_log() -> Result<(), DatabaseOpenError> {
+ unsafe {
+ config_log(Some(|_, log| debug!("rusqlite (db): {}", log)))?;
+ }
+ Ok(())
+}
+
+fn enable_trace(connection: &mut Connection) {
+ connection.trace_v2(
+ TraceEventCodes::all(),
+ Some(|trace| match trace {
+ rusqlite::trace::TraceEvent::Stmt(stmt_ref, _) => {
+ debug!(
+ "rusqlite (db) statement: {}",
+ stmt_ref.expanded_sql().unwrap_or_default()
+ )
+ }
+ rusqlite::trace::TraceEvent::Profile(stmt_ref, duration) => {}
+ rusqlite::trace::TraceEvent::Row(stmt_ref) => {
+ debug!(
+ "rusqlite (db) row: {}",
+ stmt_ref.expanded_sql().unwrap_or_default()
+ )
+ }
+ rusqlite::trace::TraceEvent::Close(conn_ref) => {
+ debug!(
+ "rusqlite (db) connection closed: {}",
+ conn_ref.db_filename().unwrap_or_default()
+ )
+ }
+ _ => {}
+ }),
+ );
+}
+
impl Db {
#[cfg(not(target_arch = "wasm32"))]
pub async fn create_connect_and_migrate(
@@ -341,22 +383,6 @@ impl Db {
result
}
- pub(crate) async fn update_chat_correspondent(
- &self,
- old_chat: Chat,
- new_correspondent: BareJID,
- ) -> Result<Chat, Error> {
- let (result, recv) = oneshot::channel();
- let command = DbCommand::UpdateChatCorrespondent {
- old_chat,
- new_correspondent,
- result,
- };
- self.sender.send(command);
- let result = recv.await?;
- result
- }
-
// pub(crate) async fn update_chat
pub(crate) async fn delete_chat(&self, chat: BareJID) -> Result<(), Error> {
@@ -416,7 +442,7 @@ impl Db {
&self,
message: Message,
chat: BareJID,
- from: FullJID,
+ from: BareJID,
) -> Result<(), Error> {
let (result, recv) = oneshot::channel();
let command = DbCommand::CreateMessage {
@@ -438,28 +464,6 @@ impl Db {
result
}
- /// create direct message from incoming. MUST upsert chat and user
- #[tracing::instrument]
- pub(crate) async fn create_message_with_user_resource(
- &self,
- message: Message,
- // TODO: enforce two kinds of jid. bare and full
- chat: BareJID,
- from: FullJID,
- ) -> Result<(), Error> {
- tracing::info!("MSGDEBUG create_message_with_user_resource exists");
- let (result, recv) = oneshot::channel();
- let command = DbCommand::CreateMessageWithUserResource {
- message,
- chat,
- from,
- result,
- };
- self.sender.send(command);
- let result = recv.await?;
- result
- }
-
pub(crate) async fn update_message_delivery(
&self,
message: Uuid,
@@ -600,11 +604,6 @@ pub enum DbCommand {
chat: BareJID,
result: oneshot::Sender<Result<(), Error>>,
},
- UpdateChatCorrespondent {
- old_chat: Chat,
- new_correspondent: BareJID,
- result: oneshot::Sender<Result<Chat, Error>>,
- },
DeleteChat {
chat: BareJID,
result: oneshot::Sender<Result<(), Error>>,
@@ -632,19 +631,13 @@ pub enum DbCommand {
CreateMessage {
message: Message,
chat: BareJID,
- from: FullJID,
+ from: BareJID,
result: oneshot::Sender<Result<(), Error>>,
},
UpsertChatAndUser {
chat: BareJID,
result: oneshot::Sender<Result<bool, Error>>,
},
- CreateMessageWithUserResource {
- message: Message,
- chat: BareJID,
- from: FullJID,
- result: oneshot::Sender<Result<(), Error>>,
- },
UpdateMessageDelivery {
message: Uuid,
delivery: Delivery,
@@ -710,7 +703,6 @@ impl Display for DbCommand {
DbCommand::CreateChat { chat, result } => "CreateChat",
DbCommand::ReadChat { chat, result } => "ReadChat",
DbCommand::MarkChatAsChatted { chat, result } => "MarkChatAsChatted",
- DbCommand::UpdateChatCorrespondent { old_chat, new_correspondent, result } => "UpdateChatCorrespondent",
DbCommand::DeleteChat { chat, result } => "DeleteChat",
DbCommand::ReadChats { result } => "ReadChats",
DbCommand::ReadChatsOrdered { result } => "ReadChatsOrdered",
@@ -718,7 +710,6 @@ impl Display for DbCommand {
DbCommand::ReadChatsOrderedWithLatestMessagesAndUsers { result } => "ReadChatsOrderedWithLatestMessagesAndUsers",
DbCommand::CreateMessage { message, chat, from, result } => "CreateMessage",
DbCommand::UpsertChatAndUser { chat, result } => "UpsertChatAndUser",
- DbCommand::CreateMessageWithUserResource { message, chat, from, result } => "CreateMessageWithUserResource",
DbCommand::UpdateMessageDelivery { message, delivery, result } => "UpdateMessageDelivery",
DbCommand::DeleteMessage { message, result } => "DeleteMessage",
DbCommand::ReadMessage { message, result } => "ReadMessage",
@@ -760,7 +751,9 @@ impl DbActor {
// let db = SqlitePool::connect(&url).await?;
// migrate!().run(&db).await?;
// Ok(Self { db })
- let db = Connection::open(url)?;
+ unsafe { enable_log()? }
+ let mut db = Connection::open(url)?;
+ enable_trace(&mut db);
db.execute_batch(include_str!("../migrations/1.sql"))?;
Ok(Self { db, receiver })
}
@@ -770,7 +763,9 @@ impl DbActor {
pub(crate) fn new_memory(
receiver: mpsc::UnboundedReceiver<DbCommand>,
) -> Result<Self, DatabaseOpenError> {
- let db = Connection::open_in_memory()?;
+ unsafe { enable_log()? }
+ let mut db = Connection::open_in_memory()?;
+ enable_trace(&mut db);
db.execute_batch(include_str!("../migrations/1.sql"))?;
Ok(Self { db, receiver })
}
@@ -780,7 +775,9 @@ impl DbActor {
pub fn new_memory(
receiver: mpsc::UnboundedReceiver<DbCommand>,
) -> Result<Self, DatabaseOpenError> {
- let db = Connection::open("mem.db")?;
+ unsafe { enable_log()? }
+ let mut db = Connection::open("mem.db")?;
+ enable_trace(&mut db);
db.execute_batch(include_str!("../migrations/1.sql"))?;
Ok(Self { db, receiver })
}
@@ -791,7 +788,9 @@ impl DbActor {
file_name: impl AsRef<Path>,
receiver: mpsc::UnboundedReceiver<DbCommand>,
) -> Result<Self, DatabaseOpenError> {
- let db = Connection::open(file_name)?;
+ unsafe { enable_log()? }
+ let mut db = Connection::open(file_name)?;
+ enable_trace(&mut db);
db.execute_batch(include_str!("../migrations/1.sql"))?;
Ok(Self { db, receiver })
}
@@ -865,13 +864,6 @@ impl DbActor {
DbCommand::MarkChatAsChatted { chat, result } => {
result.send(self.mark_chat_as_chatted(chat));
}
- DbCommand::UpdateChatCorrespondent {
- old_chat,
- new_correspondent,
- result,
- } => {
- result.send(self.update_chat_correspondent(old_chat, new_correspondent));
- }
DbCommand::DeleteChat { chat, result } => {
result.send(self.delete_chat(chat));
}
@@ -898,14 +890,6 @@ impl DbActor {
DbCommand::UpsertChatAndUser { chat, result } => {
result.send(self.upsert_chat_and_user(&chat));
}
- DbCommand::CreateMessageWithUserResource {
- message,
- chat,
- from,
- result,
- } => {
- result.send(self.create_message_with_user_resource(message, chat, from));
- }
DbCommand::UpdateMessageDelivery {
message,
delivery,
@@ -1059,7 +1043,7 @@ impl DbActor {
}
}
- // TODO: use references everywhere
+ // TODO: use references everywhere?
pub(crate) fn update_user(&self, user: User) -> Result<(), Error> {
self.db.execute(
"update users set nick = ?1, avatar = ?2 where user_jid = ?1",
@@ -1260,23 +1244,25 @@ impl DbActor {
}
pub(crate) fn create_chat(&self, chat: Chat) -> Result<(), Error> {
- let id = Uuid::new_v4();
let jid = chat.correspondent();
+ debug!("aick: before user identity upsert {jid}");
+ let identity = self.upsert_user_identity(jid)?;
+ debug!("aick: chat user identity: {identity}");
+ let id = Uuid::new_v4();
+ debug!("aick: chat uuid: {id}");
self.db.execute(
"insert into chats (id, correspondent, have_chatted) values (?1, ?2, ?3)",
- (id, jid, chat.have_chatted),
+ (id, identity, chat.have_chatted),
)?;
Ok(())
}
- // TODO: what happens if a correspondent changes from a user to a contact? maybe just have correspondent be a user, then have the client make the user show up as a contact in ui if they are in the loaded roster.
-
/// TODO: this is NOT a read
pub(crate) fn read_chat(&self, chat: BareJID) -> Result<Chat, Error> {
let chat_opt = self
.db
.query_row(
- "select correspondent, have_chatted from chats where correspondent = ?1",
+ "select primary_jid, have_chatted from chats join identities on correspondent = identities.id where primary_jid = ?1",
[&chat],
|row| {
Ok(Chat {
@@ -1302,7 +1288,7 @@ impl DbActor {
pub(crate) fn read_chat_and_user(&self, chat: BareJID) -> Result<(Chat, User), Error> {
let user = self.read_user(chat.clone())?;
let chat_opt = self.db.query_row(
- "select correspondent, have_chatted, jid, nick, avatar from chats join users on correspondent = jid where correspondent = ?1",
+ "select primary_jid, have_chatted, jid, nick, avatar from chats join identities i on chats.correspondent = i.id join users on jid = primary_jid where primary_jid = ?1",
[&chat],
|row| {
Ok((
@@ -1325,7 +1311,9 @@ impl DbActor {
correspondent: chat,
have_chatted: false,
};
+ debug!("aick: creating chat");
self.create_chat(chat.clone())?;
+ debug!("aick: created chat");
Ok((chat, user))
}
}
@@ -1333,35 +1321,15 @@ impl DbActor {
pub(crate) fn mark_chat_as_chatted(&self, chat: BareJID) -> Result<(), Error> {
self.db.execute(
- "update chats set have_chatted = true where correspondent = ?1",
+ "update chats set have_chatted = true where correspondent = (select id from identities where primary_jid = ?1)",
[chat],
)?;
Ok(())
}
- pub(crate) fn update_chat_correspondent(
- &self,
- old_chat: Chat,
- new_correspondent: BareJID,
- ) -> Result<Chat, Error> {
- let new_jid = &new_correspondent;
- let old_jid = old_chat.correspondent();
- let chat = self.db.query_row(
- "update chats set correspondent = ?1 where correspondent = ?2 returning correspondent, have_chatted",
- [new_jid, old_jid],
- |row| Ok(Chat {
- correspondent: row.get(0)?,
- have_chatted: row.get(1)?,
- })
- )?;
- Ok(chat)
- }
-
- // pub(crate) fn update_chat
-
pub(crate) fn delete_chat(&self, chat: BareJID) -> Result<(), Error> {
self.db
- .execute("delete from chats where correspondent = ?1", [chat])?;
+ .execute("delete from chats where correspondent = (select id from identities where primary_jid = ?1)", [chat])?;
Ok(())
}
@@ -1369,7 +1337,7 @@ impl DbActor {
pub(crate) fn read_chats(&self) -> Result<Vec<Chat>, Error> {
let chats = self
.db
- .prepare("select correspondent, have_chatted from chats")?
+ .prepare("select primary_jid, have_chatted from chats join identities on correspondent = identities.id")?
.query_map([], |row| {
Ok(Chat {
correspondent: row.get(0)?,
@@ -1385,7 +1353,7 @@ impl DbActor {
pub(crate) fn read_chats_ordered(&self) -> Result<Vec<Chat>, Error> {
let chats = self
.db
- .prepare("select c.correspondent, c.have_chatted, m.* from chats c join (select chat_id, max(timestamp) max_timestamp from messages group by chat_id) max_timestamps on c.id = max_timestamps.chat_id join messages m on max_timestamps.chat_id = m.chat_id and max_timestamps.max_timestamp = m.timestamp order by m.timestamp desc")?
+ .prepare("select i.primary_jid, c.have_chatted, m.* from chats c join identities i on c.correspondent = i.id join (select chat_id, max(timestamp) max_timestamp from messages group by chat_id) max_timestamps on c.id = max_timestamps.chat_id join messages m on max_timestamps.chat_id = m.chat_id and max_timestamps.max_timestamp = m.timestamp order by m.timestamp desc")?
.query_map([], |row| {
Ok(Chat {
correspondent: row.get(0)?,
@@ -1401,9 +1369,32 @@ impl DbActor {
pub(crate) fn read_chats_ordered_with_latest_messages(
&self,
) -> Result<Vec<(Chat, Message)>, Error> {
- let chats = self
+ let mut chats = self
.db
- .prepare("select c.correspondent, c.have_chatted, m.id, m.from_jid, m.delivery, m.timestamp, m.body from chats c join (select chat_id, max(timestamp) max_timestamp from messages group by chat_id) max_timestamps on c.id = max_timestamps.chat_id join messages m on max_timestamps.chat_id = m.chat_id and max_timestamps.max_timestamp = m.timestamp order by m.timestamp desc")?
+ .prepare(
+ "
+SELECT ci.primary_jid,
+ c.have_chatted,
+ m.id,
+ ui.primary_jid,
+ m.delivery,
+ m.timestamp,
+ m.body
+FROM chats c
+ JOIN (SELECT chat_id,
+ Max(timestamp) max_timestamp
+ FROM messages
+ GROUP BY chat_id) max_timestamps
+ ON c.id = max_timestamps.chat_id
+ JOIN messages m
+ ON max_timestamps.chat_id = m.chat_id
+ AND max_timestamps.max_timestamp = m.timestamp
+ JOIN identities AS ci
+ ON ci.id = c.correspondent
+ JOIN identities AS ui
+ ON ui.id = m.from_identity
+ORDER BY m.timestamp DESC",
+ )?
.query_map([], |row| {
Ok((
Chat {
@@ -1415,10 +1406,10 @@ impl DbActor {
from: row.get(3)?,
delivery: row.get(4)?,
timestamp: row.get(5)?,
- body: Body {
- body: row.get(6)?,
- },
- }
+ body: Body { body: row.get(6)? },
+ // TODO: query raw sources.
+ source: Vec::new(),
+ },
))
})?
.collect::<Result<Vec<_>, _>>()?;
@@ -1432,7 +1423,41 @@ impl DbActor {
) -> Result<Vec<((Chat, User), (Message, User))>, Error> {
let chats = self
.db
- .prepare("select c.id as chat_id, c.correspondent as chat_correspondent, c.have_chatted as chat_have_chatted, m.id as message_id, m.body as message_body, m.delivery as message_delivery, m.timestamp as message_timestamp, m.from_jid as message_from_jid, cu.jid as chat_user_jid, cu.nick as chat_user_nick, cu.avatar as chat_user_avatar, mu.jid as message_user_jid, mu.nick as message_user_nick, mu.avatar as message_user_avatar from chats c join (select chat_id, max(timestamp) max_timestamp from messages group by chat_id) max_timestamps on c.id = max_timestamps.chat_id join messages m on max_timestamps.chat_id = m.chat_id and max_timestamps.max_timestamp = m.timestamp join users as cu on cu.jid = c.correspondent join users as mu on mu.jid = m.from_jid order by m.timestamp desc")?
+ .prepare(
+ "
+SELECT c.id AS chat_id,
+ ci.primary_jid AS chat_correspondent,
+ c.have_chatted AS chat_have_chatted,
+ m.id AS message_id,
+ m.body AS message_body,
+ m.delivery AS message_delivery,
+ m.timestamp AS message_timestamp,
+ ui.primary_jid AS message_from_jid,
+ cu.jid AS chat_user_jid,
+ cu.nick AS chat_user_nick,
+ cu.avatar AS chat_user_avatar,
+ mu.jid AS message_user_jid,
+ mu.nick AS message_user_nick,
+ mu.avatar AS message_user_avatar
+FROM chats c
+ JOIN (SELECT chat_id,
+ Max(timestamp) max_timestamp
+ FROM messages
+ GROUP BY chat_id) max_timestamps
+ ON c.id = max_timestamps.chat_id
+ JOIN messages m
+ ON max_timestamps.chat_id = m.chat_id
+ AND max_timestamps.max_timestamp = m.timestamp
+ JOIN identities AS ci
+ ON ci.id = c.correspondent
+ JOIN identities AS ui
+ ON ui.id = m.from_identity
+ JOIN users AS cu
+ ON cu.jid = ci.primary_jid
+ JOIN users AS mu
+ ON mu.jid = ui.primary_jid
+ORDER BY m.timestamp DESC",
+ )?
.query_map([], |row| {
Ok((
(
@@ -1444,7 +1469,7 @@ impl DbActor {
jid: row.get("chat_user_jid")?,
nick: row.get("chat_user_nick")?,
avatar: row.get("chat_user_avatar")?,
- }
+ },
),
(
Message {
@@ -1455,12 +1480,14 @@ impl DbActor {
body: Body {
body: row.get("message_body")?,
},
+ // TODO: query raw sources.
+ source: Vec::new(),
},
User {
jid: row.get("message_user_jid")?,
nick: row.get("message_user_nick")?,
avatar: row.get("message_user_avatar")?,
- }
+ },
),
))
})?
@@ -1471,18 +1498,18 @@ impl DbActor {
#[tracing::instrument]
fn read_chat_id(&self, chat: BareJID) -> Result<Uuid, Error> {
let chat_id = self.db.query_row(
- "select id from chats where correspondent = ?1",
+ "select id from chats where correspondent = (select id from identities where primary_jid = ?1)",
[chat],
|row| Ok(row.get(0)?),
)?;
Ok(chat_id)
}
- fn read_chat_id_opt(&self, chat: JID) -> Result<Option<Uuid>, Error> {
+ fn read_chat_id_opt(&self, chat: BareJID) -> Result<Option<Uuid>, Error> {
let chat_id = self
.db
.query_row(
- "select id from chats where correspondent = ?1",
+ "select id from chats where correspondent = (select id from identities where primary_jid = ?1)",
[chat],
|row| Ok(row.get(0)?),
)
@@ -1491,57 +1518,60 @@ impl DbActor {
}
/// if the chat doesn't already exist, it must be created by calling create_chat() before running this function.
+ /// create direct message from incoming. MUST upsert user w/ identity, and chat w/identity
#[tracing::instrument]
pub(crate) fn create_message(
&self,
message: Message,
chat: BareJID,
- from: FullJID,
+ from: BareJID,
) -> Result<(), Error> {
- let from_jid = from.as_bare();
- let chat_id = self.read_chat_id(chat)?;
+ debug!("oomla: 1");
+ let chat_identity = self.upsert_user_identity(&chat)?;
+ debug!("oomla: upserted chat user and got identity {chat_identity}");
+ let (chat_id, _) = self.upsert_chat(chat_identity)?;
+ debug!("oomla: upserted chat and got chat id {chat_id}");
+ let from_identity = self.upsert_user_identity(&from)?;
+ debug!("oomla: upserted from user and got user identity {from_identity}");
tracing::debug!("creating message");
- self.db.execute("insert into messages (id, body, chat_id, from_jid, from_resource, timestamp, delivery) values (?1, ?2, ?3, ?4, ?5, ?6, ?7)", (&message.id, &message.body.body, &chat_id, &from_jid, &from.resourcepart, &message.timestamp, &message.delivery))?;
+ self.db.execute("insert into messages (id, body, chat_id, from_identity, timestamp, delivery) values (?1, ?2, ?3, ?4, ?5, ?6)", (&message.id, &message.body.body, &chat_id, &from_identity, &message.timestamp, &message.delivery))?;
Ok(())
}
- pub(crate) fn upsert_chat_and_user(&self, chat: &BareJID) -> Result<bool, Error> {
- let db = &self.db;
- db.execute(
+ // returns the user identity
+ pub(crate) fn upsert_user_identity(&self, chat: &BareJID) -> Result<Uuid, Error> {
+ self.db.execute(
"insert into users (jid) values (?1) on conflict do nothing",
[&chat],
)?;
- let id = Uuid::new_v4();
- db.execute("insert into chats (id, correspondent, have_chatted) values (?1, ?2, ?3) on conflict do nothing", (id, &chat, false))?;
- let chat = db.query_row(
- "select correspondent, have_chatted from chats where correspondent = ?1",
+ let identity = Uuid::new_v4();
+ self.db.execute(
+ "insert into identities (id, primary_jid) values (?1, ?2) on conflict do nothing",
+ (identity, &chat),
+ )?;
+ let identity = self.db.query_row(
+ "select id from identities where primary_jid = ?1",
[&chat],
- |row| {
- Ok(Chat {
- correspondent: row.get(0)?,
- have_chatted: row.get(1)?,
- })
- },
+ |row| Ok(row.get(0)?),
)?;
- Ok(chat.have_chatted)
+ Ok(identity)
}
- /// create direct message from incoming. MUST upsert chat and user
- #[tracing::instrument]
- pub(crate) fn create_message_with_user_resource(
- &self,
- message: Message,
- chat: BareJID,
- from: FullJID,
- ) -> Result<(), Error> {
- let from_jid = from.as_bare();
- tracing::debug!("creating resource");
- self.db.execute(
- "insert into resources (bare_jid, resource) values (?1, ?2) on conflict do nothing",
- (&from_jid, &from.resourcepart),
+ pub(crate) fn upsert_chat(&self, identity: Uuid) -> Result<(Uuid, bool), Error> {
+ let chat_id = Uuid::new_v4();
+ self.db.execute("insert into chats (id, correspondent, have_chatted) values (?1, ?2, ?3) on conflict do nothing", (chat_id, &identity, false))?;
+ let (chat_id, have_chatted) = self.db.query_row(
+ "select id, have_chatted from chats where correspondent = ?1",
+ [identity],
+ |row| Ok((row.get(0)?, row.get(1)?)),
)?;
- self.create_message(message, chat, from)?;
- Ok(())
+ Ok((chat_id, have_chatted))
+ }
+
+ pub(crate) fn upsert_chat_and_user(&self, chat: &BareJID) -> Result<bool, Error> {
+ let chat_identity = self.upsert_user_identity(&chat)?;
+ let (_chat_id, have_chatted) = self.upsert_chat(chat_identity)?;
+ Ok(have_chatted)
}
pub(crate) fn update_message_delivery(
@@ -1576,7 +1606,7 @@ impl DbActor {
pub(crate) fn read_message(&self, message: Uuid) -> Result<Message, Error> {
let message = self.db.query_row(
- "select id, from_jid, delivery, timestamp, body from messages where id = ?1",
+ "select id, primary_jid, delivery, timestamp, body from messages join identities on identities.id = messages.from_identity where messages.id = ?1",
[&message],
|row| {
Ok(Message {
@@ -1586,6 +1616,8 @@ impl DbActor {
delivery: row.get(2)?,
timestamp: row.get(3)?,
body: Body { body: row.get(4)? },
+ // TODO: query raw sources
+ source: Vec::new(),
})
},
)?;
@@ -1598,16 +1630,17 @@ impl DbActor {
let messages = self
.db
.prepare(
- "select id, from_jid, delivery, timestamp, body from messages where chat_id = ?1",
+ "select id, primary_jid, delivery, timestamp, body from messages join identities on identities.id = messages.from_identity where chat_id = (select id from identities where primary_jid = ?1)",
)?
.query_map([chat_id], |row| {
Ok(Message {
id: row.get(0)?,
- // TODO: full from
from: row.get(1)?,
delivery: row.get(2)?,
timestamp: row.get(3)?,
body: Body { body: row.get(4)? },
+ // TODO: query raw sources
+ source: Vec::new(),
})
})?
.collect::<Result<Vec<_>, _>>()?;
@@ -1622,7 +1655,7 @@ impl DbActor {
let messages = self
.db
.prepare(
- "select id, from_jid, delivery, timestamp, body, jid, nick, avatar from messages join users on jid = from_jid where chat_id = ? order by timestamp asc",
+ "select id, jid, delivery, timestamp, body, jid, nick, avatar from messages join users on jid = (select primary_jid from identities where id = from_identity) where chat_id = ? order by timestamp asc",
)?
.query_map([chat_id], |row| {
Ok((
@@ -1633,6 +1666,8 @@ impl DbActor {
delivery: row.get(2)?,
timestamp: row.get(3)?,
body: Body { body: row.get(4)? },
+ // TODO: query raw sources
+ source: Vec::new(),
},
User {
jid: row.get(5)?,
diff --git a/filamento/src/disco.rs b/filamento/src/disco.rs
index 580f647..f9a51b1 100644
--- a/filamento/src/disco.rs
+++ b/filamento/src/disco.rs
@@ -1,3 +1,7 @@
+// SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
use jid::JID;
use stanza::xep_0030::{info, items};
diff --git a/filamento/src/error.rs b/filamento/src/error.rs
index fb7d778..be7af92 100644
--- a/filamento/src/error.rs
+++ b/filamento/src/error.rs
@@ -1,3 +1,7 @@
+// SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
use std::{num::TryFromIntError, string::FromUtf8Error, sync::Arc};
use base64::DecodeError;
diff --git a/filamento/src/files.rs b/filamento/src/files.rs
index dcc9cd2..bd8daba 100644
--- a/filamento/src/files.rs
+++ b/filamento/src/files.rs
@@ -1,3 +1,7 @@
+// SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
use std::{
collections::HashMap,
convert::Infallible,
diff --git a/filamento/src/files/opfs.rs b/filamento/src/files/opfs.rs
index 0bcce35..41acc71 100644
--- a/filamento/src/files/opfs.rs
+++ b/filamento/src/files/opfs.rs
@@ -1,3 +1,7 @@
+// SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
use std::path::Path;
use thiserror::Error;
diff --git a/filamento/src/lib.rs b/filamento/src/lib.rs
index d3033b7..40a2867 100644
--- a/filamento/src/lib.rs
+++ b/filamento/src/lib.rs
@@ -1,3 +1,7 @@
+// SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
use std::{
collections::HashMap,
ops::{Deref, DerefMut},
@@ -803,13 +807,12 @@ mod tests {
use crate::chat;
use crate::files::FilesMem;
- use std::path::Path;
use tokio_with_wasm::alias as tokio;
#[wasm_bindgen_test]
async fn login() -> () {
tracing_wasm::set_as_global_default();
- let db = Db::create_connect_and_migrate(Path::new("./filamento.db"))
+ let db = Db::create_connect_and_migrate("./filamento.db")
.await
.unwrap();
let (client, mut recv) = Client::new(
diff --git a/filamento/src/logic/abort.rs b/filamento/src/logic/abort.rs
index 3588b13..1de905a 100644
--- a/filamento/src/logic/abort.rs
+++ b/filamento/src/logic/abort.rs
@@ -1,3 +1,7 @@
+// SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
use lampada::error::ReadError;
use crate::files::FileStore;
diff --git a/filamento/src/logic/connect.rs b/filamento/src/logic/connect.rs
index 6e392f1..a4d6f72 100644
--- a/filamento/src/logic/connect.rs
+++ b/filamento/src/logic/connect.rs
@@ -1,3 +1,7 @@
+// SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
use lampada::{Connected, Logic, error::WriteError};
use tokio::sync::oneshot;
use tracing::debug;
diff --git a/filamento/src/logic/connection_error.rs b/filamento/src/logic/connection_error.rs
index 36c1cef..7cb39b6 100644
--- a/filamento/src/logic/connection_error.rs
+++ b/filamento/src/logic/connection_error.rs
@@ -1,3 +1,7 @@
+// SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
use lampada::error::ConnectionError;
use crate::files::FileStore;
diff --git a/filamento/src/logic/disconnect.rs b/filamento/src/logic/disconnect.rs
index ebcfd4f..bbff8be 100644
--- a/filamento/src/logic/disconnect.rs
+++ b/filamento/src/logic/disconnect.rs
@@ -1,3 +1,7 @@
+// SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
use lampada::Connected;
use stanza::client::Stanza;
diff --git a/filamento/src/logic/local_only.rs b/filamento/src/logic/local_only.rs
index 7f3a2e6..5dc8793 100644
--- a/filamento/src/logic/local_only.rs
+++ b/filamento/src/logic/local_only.rs
@@ -1,3 +1,7 @@
+// SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
use jid::{BareJID, JID};
use uuid::Uuid;
diff --git a/filamento/src/logic/mod.rs b/filamento/src/logic/mod.rs
index ddf0343..7ae0235 100644
--- a/filamento/src/logic/mod.rs
+++ b/filamento/src/logic/mod.rs
@@ -1,3 +1,7 @@
+// SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
use std::{collections::HashMap, sync::Arc};
use jid::{BareJID, JID};
diff --git a/filamento/src/logic/offline.rs b/filamento/src/logic/offline.rs
index aa84f3d..1d79f86 100644
--- a/filamento/src/logic/offline.rs
+++ b/filamento/src/logic/offline.rs
@@ -1,3 +1,7 @@
+// SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
use std::process::id;
use chrono::Utc;
@@ -163,21 +167,14 @@ pub async fn handle_offline_result<Fs: FileStore + Clone>(
delivery: Some(Delivery::Failed),
timestamp,
body,
+ source: Vec::new(),
};
// try to store in message history that there is a new message that is sending. if client is quit mid-send then can mark as failed and re-send
// TODO: mark these as potentially failed upon client launch
if let Err(e) = logic
.db()
// TODO: can create message without a resource....
- .create_message_with_user_resource(
- message.clone(),
- jid.clone(),
- // TODO: when message is queued and sent, the from must also be updated with the correct resource
- FullJID {
- bare_jid: logic.jid.clone(),
- resourcepart: "unsent".to_string(),
- },
- )
+ .create_message(message.clone(), jid.clone(), logic.jid.clone())
.await
{
// TODO: should these really be handle_error or just the error macro?
diff --git a/filamento/src/logic/online.rs b/filamento/src/logic/online.rs
index b36f9a9..d49a844 100644
--- a/filamento/src/logic/online.rs
+++ b/filamento/src/logic/online.rs
@@ -1,3 +1,7 @@
+// SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
use std::{io::Cursor, time::Duration};
use base64::{prelude::BASE64_STANDARD, Engine};
@@ -520,13 +524,15 @@ pub async fn handle_send_message<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>,
body: body.clone(),
timestamp,
delivery: Some(Delivery::Sending),
+ // TODO: raw stanza logging
+ source: Vec::new(),
};
// try to store in message history that there is a new message that is sending. if client is quit mid-send then can mark as failed and re-send
// TODO: mark these as potentially failed upon client launch
if let Err(e) = logic
.db()
- .create_message_with_user_resource(message.clone(), jid.clone(), connection.jid().clone())
+ .create_message(message.clone(), jid.clone(), connection.jid().to_bare())
.await
{
// TODO: should these really be handle_error or just the error macro?
@@ -586,6 +592,7 @@ pub async fn handle_send_message<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>,
match result {
Ok(_) => {
info!("sent message: {:?}", message_stanza);
+ // TODO: raw stanza
if let Err(e) = logic.db().update_message_delivery(id, Delivery::Written).await {
error!("updating message delivery: {}", e);
}
diff --git a/filamento/src/logic/process_stanza.rs b/filamento/src/logic/process_stanza.rs
index 67b0d3f..dab475d 100644
--- a/filamento/src/logic/process_stanza.rs
+++ b/filamento/src/logic/process_stanza.rs
@@ -1,3 +1,7 @@
+// SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
use std::str::FromStr;
use base64::{Engine, prelude::BASE64_STANDARD};
@@ -76,31 +80,19 @@ pub async fn recv_message<Fs: FileStore + Clone>(
body: body.body.unwrap_or_default(),
},
delivery: None,
+ // TODO: log raw stanza
+ source: Vec::new(),
};
// TODO: process message type="error"
// save the message to the database
- match logic.db().upsert_chat_and_user(from.to_bare()).await {
- Ok(_) => match from.as_full() {
- Ok(from) => {
- if let Err(e) = logic
- .db()
- .create_message_with_user_resource(
- message.clone(),
- from.to_bare(),
- from.clone(),
- )
- .await
- {
- error!("failed to create message: {}", e);
- }
- }
- Err(e) => error!("failed to create message: {}", e),
- },
- Err(e) => {
- error!("failed to upsert chat and user: {}", e);
- }
- };
+ if let Err(e) = logic
+ .db()
+ .create_message(message.clone(), from.to_bare(), from.to_bare())
+ .await
+ {
+ error!("failed to create message: {}", e);
+ }
let from_user = match logic.db().read_user(from.to_bare()).await {
Ok(u) => u,
diff --git a/filamento/src/pep.rs b/filamento/src/pep.rs
index 3cd243f..4985eeb 100644
--- a/filamento/src/pep.rs
+++ b/filamento/src/pep.rs
@@ -1,3 +1,7 @@
+// SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
use crate::avatar::{Data as AvatarData, Metadata as AvatarMetadata};
#[derive(Clone, Debug)]
diff --git a/filamento/src/presence.rs b/filamento/src/presence.rs
index de4dd7c..5ebf8af 100644
--- a/filamento/src/presence.rs
+++ b/filamento/src/presence.rs
@@ -1,3 +1,7 @@
+// SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
use chrono::{DateTime, Utc};
use rusqlite::{
ToSql,
diff --git a/filamento/src/roster.rs b/filamento/src/roster.rs
index 0498278..70f5bca 100644
--- a/filamento/src/roster.rs
+++ b/filamento/src/roster.rs
@@ -1,3 +1,7 @@
+// SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
use std::{collections::HashSet, fmt::Display};
use jid::BareJID;
diff --git a/filamento/src/user.rs b/filamento/src/user.rs
index f962a4c..dc632ea 100644
--- a/filamento/src/user.rs
+++ b/filamento/src/user.rs
@@ -1,3 +1,7 @@
+// SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
use jid::BareJID;
#[derive(Debug, Clone, PartialEq, Eq)]