aboutsummaryrefslogtreecommitdiffstats
path: root/filamento
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--filamento/.cargo/config.toml4
-rw-r--r--filamento/.gitignore4
-rw-r--r--filamento/Cargo.toml7
-rw-r--r--filamento/README.md6
-rw-r--r--filamento/examples/example.rs4
-rw-r--r--filamento/migrations/1.sql86
-rw-r--r--filamento/src/avatar.rs4
-rw-r--r--filamento/src/caps.rs6
-rw-r--r--filamento/src/chat.rs50
-rw-r--r--filamento/src/db.rs492
-rw-r--r--filamento/src/disco.rs4
-rw-r--r--filamento/src/error.rs121
-rw-r--r--filamento/src/files.rs4
-rw-r--r--filamento/src/files/opfs.rs6
-rw-r--r--filamento/src/lib.rs133
-rw-r--r--filamento/src/logic/abort.rs4
-rw-r--r--filamento/src/logic/connect.rs6
-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.rs18
-rw-r--r--filamento/src/logic/mod.rs14
-rw-r--r--filamento/src/logic/offline.rs30
-rw-r--r--filamento/src/logic/online.rs157
-rw-r--r--filamento/src/logic/process_stanza.rs109
-rw-r--r--filamento/src/pep.rs4
-rw-r--r--filamento/src/presence.rs4
-rw-r--r--filamento/src/roster.rs8
-rw-r--r--filamento/src/user.rs8
28 files changed, 817 insertions, 484 deletions
diff --git a/filamento/.cargo/config.toml b/filamento/.cargo/config.toml
index 319101a..6166dc4 100644
--- a/filamento/.cargo/config.toml
+++ b/filamento/.cargo/config.toml
@@ -1,3 +1,7 @@
+# SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden>
+#
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
[build]
rustflags = [
# LLD (shipped with the Rust toolchain) is used as the default linker
diff --git a/filamento/.gitignore b/filamento/.gitignore
index 52acf71..bcab57c 100644
--- a/filamento/.gitignore
+++ b/filamento/.gitignore
@@ -1,3 +1,7 @@
+# SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden>
+#
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
filamento.db
./files/
.sqlx/
diff --git a/filamento/Cargo.toml b/filamento/Cargo.toml
index 7d7af0c..246ee13 100644
--- a/filamento/Cargo.toml
+++ b/filamento/Cargo.toml
@@ -1,3 +1,7 @@
+# SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden>
+#
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
[package]
name = "filamento"
version = "0.1.0"
@@ -35,6 +39,7 @@ uuid = { workspace = true, features = ["v4"] }
rusqlite = { git = "https://github.com/Spxg/rusqlite.git", branch = "wasm-demo", features = [
"uuid",
"chrono",
+ "trace",
] }
tracing = { workspace = true }
chrono = { workspace = true }
@@ -52,6 +57,7 @@ tokio = { workspace = true, features = ["sync", "time", "rt", "fs", "io-std"] }
[target.'cfg(target_arch = "wasm32")'.dependencies]
tokio = { workspace = true, features = ["sync", "time", "rt"] }
+js-sys.workspace = true
web-sys = { workspace = true, features = [
"FileSystemDirectoryHandle",
"FileSystemWritableFileStream",
@@ -68,6 +74,7 @@ wasm-bindgen-futures = { workspace = true }
rusqlite = { git = "https://github.com/Spxg/rusqlite.git", branch = "wasm-demo", features = [
"uuid",
"chrono",
+ "trace",
"precompiled-wasm",
] }
tokio_with_wasm = { workspace = true, features = ["sync", "time", "rt"] }
diff --git a/filamento/README.md b/filamento/README.md
index 57b4135..ba906b2 100644
--- a/filamento/README.md
+++ b/filamento/README.md
@@ -1,3 +1,9 @@
+<!--
+SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden>
+
+SPDX-License-Identifier: AGPL-3.0-or-later
+-->
+
# filament
a high-level xmpp chat client using luz
diff --git a/filamento/examples/example.rs b/filamento/examples/example.rs
index 65fe166..c330459 100644
--- a/filamento/examples/example.rs
+++ b/filamento/examples/example.rs
@@ -1,3 +1,7 @@
+// SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
// use std::{path::Path, str::FromStr, sync::Arc, time::Duration};
// use filamento::{Client, db::Db, files::FileStore};
diff --git a/filamento/migrations/1.sql b/filamento/migrations/1.sql
index 502c5a9..1b40d89 100644
--- a/filamento/migrations/1.sql
+++ b/filamento/migrations/1.sql
@@ -1,9 +1,12 @@
+-- SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden>
+--
+-- SPDX-License-Identifier: AGPL-3.0-or-later
+
PRAGMA foreign_keys = on;
-- a user jid will never change, only a chat user will change
--- TODO: avatar, nick, etc.
create table if not exists users(
- -- TODO: enforce bare jid
+ -- bare jid
jid text primary key not null,
nick text,
avatar text,
@@ -12,28 +15,26 @@ create table if not exists users(
-- TODO: last_seen
);
--- -- links to messages, jabber users, stores jid history, etc.
--- create table identities(
--- id text primary key not null
--- );
-
--- create table identities_users(
--- id text not null,
--- jid text not null,
--- -- whichever has the newest timestamp is the active one.
--- -- what to do when somebody moves, but then the old jid is used again without having explicitly moved back? create new identity to assign ownership to?
--- -- merging of identities?
--- activated_timestamp not null,
--- foreign key(id) references identities(id),
--- foreign key(jid) references users(jid),
--- primary key(activated timestamp, id, jid)
--- );
-
-create table if not exists resources(
- bare_jid text not null,
- resource text not null,
- foreign key(bare_jid) references users(jid),
- primary key(bare_jid, resource)
+-- links to messages, jabber users, stores jid history, etc.
+-- every identity requires a user, so imported messages must be assigned to an existing jabber account.
+create table if not exists identities(
+ id text primary key not null,
+ -- each time a user moves, their identity's primary jid is updated, and the new jid is added to the identities log
+ primary_jid text not null unique,
+ foreign key(primary_jid) references users(jid)
+);
+-- this also allows e.g. mucs with imported histories to assign messages temporarily to muc-generated temporary users, then send user move events to assign message ownership once that person joins the new group chat with a jabber account.
+
+-- identities log basically.
+create table if not exists identities_users(
+ id text not null,
+ jid text not null,
+ -- what to do when somebody moves, but then the old jid is used again without having explicitly moved back? create new identity to assign ownership to?
+ -- merging of identities?
+ activated_timestamp not null,
+ foreign key(id) references identities(id),
+ foreign key(jid) references users(jid),
+ primary key(id, jid)
);
-- enum for subscription state
@@ -71,7 +72,8 @@ create table if not exists chats (
id text primary key not null,
have_chatted bool not null,
correspondent text not null unique,
- foreign key(correspondent) references users(jid)
+ -- TODO: how many messages to store locally/rely on mam, whether to auto-download/store media too.
+ foreign key(correspondent) references identities(id)
);
-- enum for subscription state
@@ -92,27 +94,17 @@ create table if not exists messages (
-- 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: queued messages offline
- -- TODO: timestamp
timestamp text not null,
- -- TODO: icky
- -- the user to show it coming from (not necessarily the original sender)
- -- from_identity text not null,
- -- original sender details (only from jabber supported for now)
- from_jid text not null,
- -- resource can be null
- from_resource text,
- -- check (from_jid != original_sender),
+ -- the identity to show it coming from (not necessarily the original sender)
+ from_identity text not null,
+
+ -- TODO: unread message count, read bool not null,
foreign key(delivery) references delivery(state),
- -- 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)
- -- TODO: read bool not null,
foreign key(chat_id) references chats(id) on delete cascade,
- -- foreign key(from_identity) references identities(id),
- foreign key(from_jid) references users(jid),
- foreign key(from_jid, from_resource) references resources(bare_jid, resource)
+ foreign key(from_identity) references identities(id)
);
-- enum for subscription state
@@ -139,3 +131,17 @@ create table if not exists capability_hash_nodes (
);
insert into capability_hash_nodes ( node, capabilities ) values ('https://bunny.garden/filamento#mSavc/SLnHm8zazs5RlcbD/iXoc=', 'aHR0cDovL2phYmJlci5vcmcvcHJvdG9jb2wvY2Fwcx9odHRwOi8vamFiYmVyLm9yZy9wcm90b2NvbC9kaXNjbyNpbmZvH2h0dHA6Ly9qYWJiZXIub3JnL3Byb3RvY29sL2Rpc2NvI2l0ZW1zH2h0dHA6Ly9qYWJiZXIub3JnL3Byb3RvY29sL25pY2sfaHR0cDovL2phYmJlci5vcmcvcHJvdG9jb2wvbmljaytub3RpZnkfHGNsaWVudB9wYx8fZmlsYW1lbnRvIDAuMS4wHx4cHA==') on conflict do nothing;
+
+-- TODO: later maybe just a full stanza log period?
+create table if not exists message_stanzas (
+ id integer primary key not null,
+ raw text not null,
+ timestamp text not null,
+ -- should be 1 to many as the same original stanza could technically have multiple envelopes...(e.g. message sync, mam), also makes making dangling stanzas not possible easier when modeling the relationship this way.
+ envelope_of integer,
+ linked_message text,
+ -- check that stanza is either linked to another stanza, or a message (no dangling stanzas)
+ check ((envelope_of == null) <> (linked_message == null)),
+ foreign key(linked_message) references messages(id),
+ foreign key(envelope_of) references message_stanzas(id)
+)
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 43f1cf4..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};
@@ -377,7 +381,7 @@ pub fn node_to_hash(node: String) -> Result<Hash, HashNodeConversionError> {
#[cfg(test)]
mod tests {
- use peanuts::{Writer, element::IntoElement, loggable::Loggable};
+ use peanuts::Writer;
use stanza::{
xep_0004::{Field, FieldType, Value, X, XType},
xep_0030::info::{Feature, Identity},
diff --git a/filamento/src/chat.rs b/filamento/src/chat.rs
index 5f58866..687da82 100644
--- a/filamento/src/chat.rs
+++ b/filamento/src/chat.rs
@@ -1,7 +1,11 @@
+// SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
use std::fmt::{Display, Write};
use chrono::{DateTime, Utc};
-use jid::JID;
+use jid::{BareJID, JID};
use rusqlite::{
ToSql,
types::{FromSql, ToSqlOutput, Value},
@@ -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)
- pub from: JID,
+ /// 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 {
@@ -97,7 +127,7 @@ pub struct Body {
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "reactive_stores", derive(reactive_stores::Store))]
pub struct Chat {
- pub correspondent: JID,
+ pub correspondent: BareJID,
pub have_chatted: bool,
// pub unread_messages: i32,
// pub latest_message: Message,
@@ -109,13 +139,13 @@ pub struct Chat {
pub enum ChatUpdate {}
impl Chat {
- pub fn new(correspondent: JID, have_chatted: bool) -> Self {
+ pub fn new(correspondent: BareJID, have_chatted: bool) -> Self {
Self {
correspondent,
have_chatted,
}
}
- pub fn correspondent(&self) -> &JID {
+ pub fn correspondent(&self) -> &BareJID {
&self.correspondent
}
}
diff --git a/filamento/src/db.rs b/filamento/src/db.rs
index bac1b7c..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::JID;
+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(
@@ -88,12 +130,21 @@ impl Db {
spawn_blocking(move || {
spawn_local(async move {
debug!("installing opfs in spawn");
- rusqlite::ffi::install_opfs_sahpool(
+ match rusqlite::ffi::install_opfs_sahpool(
Some(&rusqlite::ffi::OpfsSAHPoolCfg::default()),
false,
)
.await
- .unwrap();
+ {
+ Ok(_) => {}
+ Err(e) => {
+ use crate::error::OpfsSAHError;
+
+ let error: OpfsSAHError = e.into();
+ result_send.send(Err(error.into()));
+ return;
+ }
+ }
debug!("opfs installed");
let file_name = format!("file:{}?vfs=opfs-sahpool", file_name.as_ref());
let result = DbActor::new(file_name, receiver);
@@ -152,7 +203,7 @@ impl Db {
}
// TODO: this is not a 'read' user
- pub(crate) async fn read_user(&self, user: JID) -> Result<User, Error> {
+ pub(crate) async fn read_user(&self, user: BareJID) -> Result<User, Error> {
let (result, recv) = oneshot::channel();
let command = DbCommand::ReadUser { user, result };
self.sender.send(command);
@@ -161,7 +212,7 @@ impl Db {
}
/// returns whether or not the nickname was updated
- pub(crate) async fn delete_user_nick(&self, jid: JID) -> Result<bool, Error> {
+ pub(crate) async fn delete_user_nick(&self, jid: BareJID) -> Result<bool, Error> {
let (result, recv) = oneshot::channel();
let command = DbCommand::DeleteUserNick { jid, result };
self.sender.send(command);
@@ -170,7 +221,7 @@ impl Db {
}
/// returns whether or not the nickname was updated
- pub(crate) async fn upsert_user_nick(&self, jid: JID, nick: String) -> Result<bool, Error> {
+ pub(crate) async fn upsert_user_nick(&self, jid: BareJID, nick: String) -> Result<bool, Error> {
let (result, recv) = oneshot::channel();
let command = DbCommand::UpsertUserNick { jid, nick, result };
self.sender.send(command);
@@ -181,7 +232,7 @@ impl Db {
/// returns whether or not the avatar was updated, and the file to delete if there existed an old avatar
pub(crate) async fn delete_user_avatar(
&self,
- jid: JID,
+ jid: BareJID,
) -> Result<(bool, Option<String>), Error> {
let (result, recv) = oneshot::channel();
let command = DbCommand::DeleteUserAvatar { jid, result };
@@ -193,7 +244,7 @@ impl Db {
/// returns whether or not the avatar was updated, and the file to delete if there existed an old avatar
pub(crate) async fn upsert_user_avatar(
&self,
- jid: JID,
+ jid: BareJID,
avatar: String,
) -> Result<(bool, Option<String>), Error> {
let (result, recv) = oneshot::channel();
@@ -228,7 +279,7 @@ impl Db {
result
}
- pub(crate) async fn read_contact(&self, contact: JID) -> Result<Contact, Error> {
+ pub(crate) async fn read_contact(&self, contact: BareJID) -> Result<Contact, Error> {
let (result, recv) = oneshot::channel();
let command = DbCommand::ReadContact { contact, result };
self.sender.send(command);
@@ -236,7 +287,10 @@ impl Db {
result
}
- pub(crate) async fn read_contact_opt(&self, contact: JID) -> Result<Option<Contact>, Error> {
+ pub(crate) async fn read_contact_opt(
+ &self,
+ contact: BareJID,
+ ) -> Result<Option<Contact>, Error> {
let (result, recv) = oneshot::channel();
let command = DbCommand::ReadContactOpt { contact, result };
self.sender.send(command);
@@ -261,7 +315,7 @@ impl Db {
result
}
- pub(crate) async fn delete_contact(&self, contact: JID) -> Result<(), Error> {
+ pub(crate) async fn delete_contact(&self, contact: BareJID) -> Result<(), Error> {
let (result, recv) = oneshot::channel();
let command = DbCommand::DeleteContact { contact, result };
self.sender.send(command);
@@ -305,7 +359,7 @@ impl Db {
// 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.
- pub(crate) async fn read_chat(&self, chat: JID) -> Result<Chat, Error> {
+ pub(crate) async fn read_chat(&self, chat: BareJID) -> Result<Chat, Error> {
let (result, recv) = oneshot::channel();
let command = DbCommand::ReadChat { chat, result };
self.sender.send(command);
@@ -313,7 +367,7 @@ impl Db {
result
}
- pub(crate) async fn read_chat_and_user(&self, chat: JID) -> Result<(Chat, User), Error> {
+ pub(crate) async fn read_chat_and_user(&self, chat: BareJID) -> Result<(Chat, User), Error> {
let (result, recv) = oneshot::channel();
let command = DbCommand::ReadChatAndUser { chat, result };
self.sender.send(command);
@@ -321,7 +375,7 @@ impl Db {
result
}
- pub(crate) async fn mark_chat_as_chatted(&self, chat: JID) -> Result<(), Error> {
+ pub(crate) async fn mark_chat_as_chatted(&self, chat: BareJID) -> Result<(), Error> {
let (result, recv) = oneshot::channel();
let command = DbCommand::MarkChatAsChatted { chat, result };
self.sender.send(command);
@@ -329,25 +383,9 @@ impl Db {
result
}
- pub(crate) async fn update_chat_correspondent(
- &self,
- old_chat: Chat,
- new_correspondent: JID,
- ) -> 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: JID) -> Result<(), Error> {
+ pub(crate) async fn delete_chat(&self, chat: BareJID) -> Result<(), Error> {
let (result, recv) = oneshot::channel();
let command = DbCommand::DeleteChat { chat, result };
self.sender.send(command);
@@ -403,8 +441,8 @@ impl Db {
pub(crate) async fn create_message(
&self,
message: Message,
- chat: JID,
- from: JID,
+ chat: BareJID,
+ from: BareJID,
) -> Result<(), Error> {
let (result, recv) = oneshot::channel();
let command = DbCommand::CreateMessage {
@@ -418,7 +456,7 @@ impl Db {
result
}
- pub(crate) async fn upsert_chat_and_user(&self, chat: JID) -> Result<bool, Error> {
+ pub(crate) async fn upsert_chat_and_user(&self, chat: BareJID) -> Result<bool, Error> {
let (result, recv) = oneshot::channel();
let command = DbCommand::UpsertChatAndUser { chat, result };
self.sender.send(command);
@@ -426,30 +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
- // must be bare jid
- chat: JID,
- // full jid
- from: JID,
- ) -> 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,
@@ -483,9 +497,9 @@ impl Db {
DeleteCachedStatus => delete_cached_status() -> ();
UpsertCachedStatus => upsert_cached_status(status: Online) -> ();
ReadCachedStatus => read_cached_status() -> Online;
- ReadMessageHistoryWithUsers => read_message_history_with_users(chat: JID) -> Vec<(Message, User)>;
+ ReadMessageHistoryWithUsers => read_message_history_with_users(chat: BareJID) -> Vec<(Message, User)>;
// TODO: paging
- ReadMessageHistory => read_message_history(chat: JID) -> Vec<Message>;
+ ReadMessageHistory => read_message_history(chat: BareJID) -> Vec<Message>;
ReadMessage => read_message(message: Uuid) -> Message;
DeleteMessage => delete_message(message: Uuid) -> ()
);
@@ -515,24 +529,24 @@ pub enum DbCommand {
result: oneshot::Sender<Result<(), Error>>,
},
ReadUser {
- user: JID,
+ user: BareJID,
result: oneshot::Sender<Result<User, Error>>,
},
DeleteUserNick {
- jid: JID,
+ jid: BareJID,
result: oneshot::Sender<Result<bool, Error>>,
},
UpsertUserNick {
- jid: JID,
+ jid: BareJID,
nick: String,
result: oneshot::Sender<Result<bool, Error>>,
},
DeleteUserAvatar {
- jid: JID,
+ jid: BareJID,
result: oneshot::Sender<Result<(bool, Option<String>), Error>>,
},
UpsertUserAvatar {
- jid: JID,
+ jid: BareJID,
avatar: String,
result: oneshot::Sender<Result<(bool, Option<String>), Error>>,
},
@@ -545,11 +559,11 @@ pub enum DbCommand {
result: oneshot::Sender<Result<(), Error>>,
},
ReadContact {
- contact: JID,
+ contact: BareJID,
result: oneshot::Sender<Result<Contact, Error>>,
},
ReadContactOpt {
- contact: JID,
+ contact: BareJID,
result: oneshot::Sender<Result<Option<Contact>, Error>>,
},
UpdateContact {
@@ -561,7 +575,7 @@ pub enum DbCommand {
result: oneshot::Sender<Result<(), Error>>,
},
DeleteContact {
- contact: JID,
+ contact: BareJID,
result: oneshot::Sender<Result<(), Error>>,
},
ReplaceCachedRoster {
@@ -579,24 +593,19 @@ pub enum DbCommand {
result: oneshot::Sender<Result<(), Error>>,
},
ReadChat {
- chat: JID,
+ chat: BareJID,
result: oneshot::Sender<Result<Chat, Error>>,
},
ReadChatAndUser {
- chat: JID,
+ chat: BareJID,
result: oneshot::Sender<Result<(Chat, User), Error>>,
},
MarkChatAsChatted {
- chat: JID,
+ chat: BareJID,
result: oneshot::Sender<Result<(), Error>>,
},
- UpdateChatCorrespondent {
- old_chat: Chat,
- new_correspondent: JID,
- result: oneshot::Sender<Result<Chat, Error>>,
- },
DeleteChat {
- chat: JID,
+ chat: BareJID,
result: oneshot::Sender<Result<(), Error>>,
},
ReadChats {
@@ -621,20 +630,14 @@ pub enum DbCommand {
// },
CreateMessage {
message: Message,
- chat: JID,
- from: JID,
+ chat: BareJID,
+ from: BareJID,
result: oneshot::Sender<Result<(), Error>>,
},
UpsertChatAndUser {
- chat: JID,
+ chat: BareJID,
result: oneshot::Sender<Result<bool, Error>>,
},
- CreateMessageWithUserResource {
- message: Message,
- chat: JID,
- from: JID,
- result: oneshot::Sender<Result<(), Error>>,
- },
UpdateMessageDelivery {
message: Uuid,
delivery: Delivery,
@@ -649,11 +652,11 @@ pub enum DbCommand {
result: oneshot::Sender<Result<Message, Error>>,
},
ReadMessageHistory {
- chat: JID,
+ chat: BareJID,
result: oneshot::Sender<Result<Vec<Message>, Error>>,
},
ReadMessageHistoryWithUsers {
- chat: JID,
+ chat: BareJID,
result: oneshot::Sender<Result<Vec<(Message, User)>, Error>>,
},
ReadCachedStatus {
@@ -700,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",
@@ -708,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",
@@ -750,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 })
}
@@ -760,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 })
}
@@ -770,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 })
}
@@ -781,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 })
}
@@ -823,7 +832,7 @@ impl DbActor {
result.send(self.read_contact(contact));
}
DbCommand::ReadContactOpt { contact, result } => {
- result.send(self.read_contact_opt(&contact));
+ result.send(self.read_contact_opt(contact));
}
DbCommand::UpdateContact { contact, result } => {
result.send(self.update_contact(contact));
@@ -855,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));
}
@@ -888,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,
@@ -951,7 +945,7 @@ impl DbActor {
}
// TODO: this is not a 'read' user
- pub(crate) fn read_user(&self, user: JID) -> Result<User, Error> {
+ pub(crate) fn read_user(&self, user: BareJID) -> Result<User, Error> {
let db = &self.db;
let user_opt = db
.query_row(
@@ -980,7 +974,7 @@ impl DbActor {
}
/// returns whether or not the nickname was updated
- pub(crate) fn delete_user_nick(&self, jid: JID) -> Result<bool, Error> {
+ pub(crate) fn delete_user_nick(&self, jid: BareJID) -> Result<bool, Error> {
let rows_affected;
{
rows_affected = self.db.execute("insert into users (jid, nick) values (?1, ?2) on conflict do update set nick = ?3 where nick is not ?4", (jid, None::<String>, None::<String>, None::<String>))?;
@@ -993,7 +987,7 @@ impl DbActor {
}
/// returns whether or not the nickname was updated
- pub(crate) fn upsert_user_nick(&self, jid: JID, nick: String) -> Result<bool, Error> {
+ pub(crate) fn upsert_user_nick(&self, jid: BareJID, nick: String) -> Result<bool, Error> {
let rows_affected;
{
rows_affected = self.db.execute("insert into users (jid, nick) values (?1, ?2) on conflict do update set nick = ?3 where nick is not ?4", (jid, &nick, &nick, &nick))?;
@@ -1006,7 +1000,7 @@ impl DbActor {
}
/// returns whether or not the avatar was updated, and the file to delete if there existed an old avatar
- pub(crate) fn delete_user_avatar(&self, jid: JID) -> Result<(bool, Option<String>), Error> {
+ pub(crate) fn delete_user_avatar(&self, jid: BareJID) -> Result<(bool, Option<String>), Error> {
let (old_avatar, rows_affected): (Option<String>, _);
{
let db = &self.db;
@@ -1027,7 +1021,7 @@ impl DbActor {
/// returns whether or not the avatar was updated, and the file to delete if there existed an old avatar
pub(crate) fn upsert_user_avatar(
&self,
- jid: JID,
+ jid: BareJID,
avatar: String,
) -> Result<(bool, Option<String>), Error> {
let (old_avatar, rows_affected): (Option<String>, _);
@@ -1049,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",
@@ -1081,7 +1075,7 @@ impl DbActor {
Ok(())
}
- pub(crate) fn read_contact(&self, contact: JID) -> Result<Contact, Error> {
+ pub(crate) fn read_contact(&self, contact: BareJID) -> Result<Contact, Error> {
let db = &self.db;
let mut contact_item = db.query_row(
"select user_jid, name, subscription from roster where user_jid = ?1",
@@ -1103,7 +1097,7 @@ impl DbActor {
Ok(contact_item)
}
- pub(crate) fn read_contact_opt(&self, contact: &JID) -> Result<Option<Contact>, Error> {
+ pub(crate) fn read_contact_opt(&self, contact: BareJID) -> Result<Option<Contact>, Error> {
let db = &self.db;
let contact_item = db
.query_row(
@@ -1183,7 +1177,7 @@ impl DbActor {
Ok(())
}
- pub(crate) fn delete_contact(&self, contact: JID) -> Result<(), Error> {
+ pub(crate) fn delete_contact(&self, contact: BareJID) -> Result<(), Error> {
self.db
.execute("delete from roster where user_jid = ?1", [&contact])?;
Ok(())
@@ -1250,30 +1244,34 @@ 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.
-
- /// should be a bare jid
/// TODO: this is NOT a read
- pub(crate) fn read_chat(&self, chat: JID) -> Result<Chat, Error> {
- let chat_opt = self.db.query_row(
- "select correspondent, have_chatted from chats where correspondent = ?1",
- [&chat],
- |row| {
- Ok(Chat {
- correspondent: row.get(0)?,
- have_chatted: row.get(1)?,
- })
- },
- ).optional()?;
+ pub(crate) fn read_chat(&self, chat: BareJID) -> Result<Chat, Error> {
+ let chat_opt = self
+ .db
+ .query_row(
+ "select primary_jid, have_chatted from chats join identities on correspondent = identities.id where primary_jid = ?1",
+ [&chat],
+ |row| {
+ Ok(Chat {
+ correspondent: row.get(0)?,
+ have_chatted: row.get(1)?,
+ })
+ },
+ )
+ .optional()?;
match chat_opt {
Some(chat) => return Ok(chat),
None => {
@@ -1287,10 +1285,10 @@ impl DbActor {
}
}
- pub(crate) fn read_chat_and_user(&self, chat: JID) -> Result<(Chat, User), Error> {
+ 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((
@@ -1313,43 +1311,25 @@ impl DbActor {
correspondent: chat,
have_chatted: false,
};
+ debug!("aick: creating chat");
self.create_chat(chat.clone())?;
+ debug!("aick: created chat");
Ok((chat, user))
}
}
}
- pub(crate) fn mark_chat_as_chatted(&self, chat: JID) -> Result<(), Error> {
+ 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: JID,
- ) -> 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: JID) -> Result<(), Error> {
+ 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(())
}
@@ -1357,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)?,
@@ -1373,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)?,
@@ -1389,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 {
@@ -1403,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<_>, _>>()?;
@@ -1420,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((
(
@@ -1432,7 +1469,7 @@ impl DbActor {
jid: row.get("chat_user_jid")?,
nick: row.get("chat_user_nick")?,
avatar: row.get("chat_user_avatar")?,
- }
+ },
),
(
Message {
@@ -1443,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")?,
- }
+ },
),
))
})?
@@ -1457,20 +1496,20 @@ impl DbActor {
}
#[tracing::instrument]
- fn read_chat_id(&self, chat: JID) -> Result<Uuid, Error> {
+ 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)?),
)
@@ -1479,64 +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: JID,
- from: JID,
+ chat: BareJID,
+ 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: &JID) -> Result<bool, Error> {
- let bare_chat = chat.as_bare();
- 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",
- [&bare_chat],
+ [&chat],
)?;
- let id = Uuid::new_v4();
- db.execute("insert into chats (id, correspondent, have_chatted) values (?1, ?2, ?3) on conflict do nothing", (id, &bare_chat, false))?;
- let chat = db.query_row(
- "select correspondent, have_chatted from chats where correspondent = ?1",
- [&bare_chat],
- |row| {
- Ok(Chat {
- correspondent: row.get(0)?,
- have_chatted: row.get(1)?,
- })
- },
+ let identity = Uuid::new_v4();
+ self.db.execute(
+ "insert into identities (id, primary_jid) values (?1, ?2) on conflict do nothing",
+ (identity, &chat),
)?;
- Ok(chat.have_chatted)
+ let identity = self.db.query_row(
+ "select id from identities where primary_jid = ?1",
+ [&chat],
+ |row| Ok(row.get(0)?),
+ )?;
+ 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,
- // TODO: enforce two kinds of jid. bare and full
- // must be bare jid
- chat: JID,
- // full jid
- from: JID,
- ) -> Result<(), Error> {
- let from_jid = from.as_bare();
- let chat = chat.as_bare();
- tracing::debug!("creating resource");
- if let Some(resource) = &from.resourcepart {
- self.db.execute(
- "insert into resources (bare_jid, resource) values (?1, ?2) on conflict do nothing",
- (&from_jid, resource),
- )?;
- }
- self.create_message(message, chat, from)?;
- Ok(())
+ 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)?)),
+ )?;
+ 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(
@@ -1571,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 {
@@ -1581,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(),
})
},
)?;
@@ -1588,21 +1625,22 @@ impl DbActor {
}
// TODO: paging
- pub(crate) fn read_message_history(&self, chat: JID) -> Result<Vec<Message>, Error> {
+ pub(crate) fn read_message_history(&self, chat: BareJID) -> Result<Vec<Message>, Error> {
let chat_id = self.read_chat_id(chat)?;
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<_>, _>>()?;
@@ -1611,13 +1649,13 @@ impl DbActor {
pub(crate) fn read_message_history_with_users(
&self,
- chat: JID,
+ chat: BareJID,
) -> Result<Vec<(Message, User)>, Error> {
let chat_id = self.read_chat_id(chat)?;
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((
@@ -1628,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 f9f9199..be7af92 100644
--- a/filamento/src/error.rs
+++ b/filamento/src/error.rs
@@ -1,8 +1,13 @@
+// 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;
use image::ImageError;
use jid::JID;
+use jid::JIDError;
use lampada::error::{ActorError, ReadError, WriteError};
use stanza::client::{Stanza, iq::Query};
use thiserror::Error;
@@ -292,6 +297,9 @@ pub enum IqProcessError {
#[derive(Debug, Error, Clone)]
pub enum DatabaseOpenError {
+ #[cfg(target_arch = "wasm32")]
+ #[error("opfs: {0}")]
+ OpfsSAH(#[from] OpfsSAHError),
#[error("error: {0}")]
Error(Arc<rusqlite::Error>),
// #[error("migration: {0}")]
@@ -310,6 +318,117 @@ pub enum DatabaseOpenError {
// }
// }
+#[cfg(target_arch = "wasm32")]
+impl From<rusqlite::ffi::OpfsSAHError> for OpfsSAHError {
+ fn from(e: rusqlite::ffi::OpfsSAHError) -> Self {
+ use wasm_bindgen::UnwrapThrowExt;
+ match e {
+ rusqlite::ffi::OpfsSAHError::Vfs(_register_vfs_error) => Self::VfsRegistration,
+ rusqlite::ffi::OpfsSAHError::ImportDb(_import_db_error) => Self::ImportDb,
+ rusqlite::ffi::OpfsSAHError::NotSuported => Self::NotSupported,
+ rusqlite::ffi::OpfsSAHError::GetDirHandle(js_value) => {
+ let error: js_sys::Error = js_value.into();
+ let message = error.message().as_string().unwrap_throw();
+ Self::GetDirHandle(message)
+ }
+ rusqlite::ffi::OpfsSAHError::GetFileHandle(js_value) => {
+ let error: js_sys::Error = js_value.into();
+ let message = error.message().as_string().unwrap_throw();
+ Self::GetFileHandle(message)
+ }
+ rusqlite::ffi::OpfsSAHError::CreateSyncAccessHandle(js_value) => {
+ let error: js_sys::Error = js_value.into();
+ let message = error.message().as_string().unwrap_throw();
+ Self::CreateSyncAccessHandle(message)
+ }
+ rusqlite::ffi::OpfsSAHError::IterHandle(js_value) => {
+ let error: js_sys::Error = js_value.into();
+ let message = error.message().as_string().unwrap_throw();
+ Self::IterHandle(message)
+ }
+ rusqlite::ffi::OpfsSAHError::GetPath(js_value) => {
+ let error: js_sys::Error = js_value.into();
+ let message = error.message().as_string().unwrap_throw();
+ Self::GetPath(message)
+ }
+ rusqlite::ffi::OpfsSAHError::RemoveEntity(js_value) => {
+ let error: js_sys::Error = js_value.into();
+ let message = error.message().as_string().unwrap_throw();
+ Self::RemoveEntity(message)
+ }
+ rusqlite::ffi::OpfsSAHError::GetSize(js_value) => {
+ let error: js_sys::Error = js_value.into();
+ let message = error.message().as_string().unwrap_throw();
+ Self::GetSize(message)
+ }
+ rusqlite::ffi::OpfsSAHError::Read(js_value) => {
+ let error: js_sys::Error = js_value.into();
+ let message = error.message().as_string().unwrap_throw();
+ Self::Read(message)
+ }
+ rusqlite::ffi::OpfsSAHError::Write(js_value) => {
+ let error: js_sys::Error = js_value.into();
+ let message = error.message().as_string().unwrap_throw();
+ Self::Write(message)
+ }
+ rusqlite::ffi::OpfsSAHError::Flush(js_value) => {
+ let error: js_sys::Error = js_value.into();
+ let message = error.message().as_string().unwrap_throw();
+ Self::Flush(message)
+ }
+ rusqlite::ffi::OpfsSAHError::Truncate(js_value) => {
+ let error: js_sys::Error = js_value.into();
+ let message = error.message().as_string().unwrap_throw();
+ Self::Truncate(message)
+ }
+ rusqlite::ffi::OpfsSAHError::Reflect(js_value) => {
+ let error: js_sys::Error = js_value.into();
+ let message = error.message().as_string().unwrap_throw();
+ Self::Reflect(message)
+ }
+ rusqlite::ffi::OpfsSAHError::Generic(s) => Self::Generic(s),
+ rusqlite::ffi::OpfsSAHError::Custom(s) => Self::Generic(s),
+ }
+ }
+}
+
+#[cfg(target_arch = "wasm32")]
+#[derive(Debug, Error, Clone)]
+pub enum OpfsSAHError {
+ #[error("VFS registration")]
+ VfsRegistration,
+ #[error("import db error")]
+ ImportDb,
+ #[error("not supported")]
+ NotSupported,
+ #[error("get dir handle: {0}")]
+ GetDirHandle(String),
+ #[error("get file handle: {0}")]
+ GetFileHandle(String),
+ #[error("create sync access handle: {0}")]
+ CreateSyncAccessHandle(String),
+ #[error("iter handle: {0}")]
+ IterHandle(String),
+ #[error("get path: {0}")]
+ GetPath(String),
+ #[error("remove entity: {0}")]
+ RemoveEntity(String),
+ #[error("get size: {0}")]
+ GetSize(String),
+ #[error("read: {0}")]
+ Read(String),
+ #[error("write: {0}")]
+ Write(String),
+ #[error("flush: {0}")]
+ Flush(String),
+ #[error("truncate: {0}")]
+ Truncate(String),
+ #[error("reflect: {0}")]
+ Reflect(String),
+ #[error("generic: {0}")]
+ Generic(String),
+}
+
impl From<std::io::Error> for DatabaseOpenError {
fn from(e: std::io::Error) -> Self {
Self::Io(Arc::new(e))
@@ -332,6 +451,8 @@ pub enum PresenceError {
MissingFrom,
#[error("stanza error: {0:?}")]
StanzaError(Option<stanza::client::error::Error>),
+ #[error("received subscription request from a non-bare jid")]
+ InvalidSubscriptionRequest(#[from] JIDError),
}
#[derive(Debug, Error, Clone)]
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 fb32c6e..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;
@@ -10,7 +14,7 @@ use web_sys::{
use crate::FileStore;
-#[derive(Clone)]
+#[derive(Clone, Debug)]
pub struct FilesOPFS {
directory: String,
}
diff --git a/filamento/src/lib.rs b/filamento/src/lib.rs
index 068bfe8..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},
@@ -16,7 +20,7 @@ use error::{
};
use files::FileStore;
use futures::FutureExt;
-use jid::JID;
+use jid::{BareJID, JID};
use lampada::{
Connected, CoreClient, CoreClientCommand, Logic, SupervisorSender, WriteMessage,
error::{ActorError, CommandError, ConnectionError, ReadError, WriteError},
@@ -68,45 +72,55 @@ pub enum Command<Fs: FileStore> {
oneshot::Sender<Result<Vec<((Chat, User), (Message, User))>, DatabaseError>>,
),
/// get a specific chat by jid
- GetChat(JID, oneshot::Sender<Result<Chat, DatabaseError>>),
+ GetChat(BareJID, oneshot::Sender<Result<Chat, DatabaseError>>),
/// get a specific chat and user by jid
- GetChatAndUser(JID, oneshot::Sender<Result<(Chat, User), DatabaseError>>),
+ GetChatAndUser(
+ BareJID,
+ oneshot::Sender<Result<(Chat, User), DatabaseError>>,
+ ),
/// get message history for chat (does appropriate mam things)
GetMessage(Uuid, oneshot::Sender<Result<Message, DatabaseError>>),
// TODO: paging and filtering
- GetMessages(JID, oneshot::Sender<Result<Vec<Message>, DatabaseError>>),
+ GetMessages(
+ BareJID,
+ oneshot::Sender<Result<Vec<Message>, DatabaseError>>,
+ ),
/// get message history for chat (does appropriate mam things)
// TODO: paging and filtering
GetMessagesWithUsers(
- JID,
+ BareJID,
oneshot::Sender<Result<Vec<(Message, User)>, DatabaseError>>,
),
/// delete a chat from your chat history, along with all the corresponding messages
- DeleteChat(JID, oneshot::Sender<Result<(), DatabaseError>>),
+ DeleteChat(BareJID, oneshot::Sender<Result<(), DatabaseError>>),
/// delete a message from your chat history
DeleteMessage(Uuid, oneshot::Sender<Result<(), DatabaseError>>),
/// get a user from your users database
- GetUser(JID, oneshot::Sender<Result<User, DatabaseError>>),
+ GetUser(BareJID, oneshot::Sender<Result<User, DatabaseError>>),
/// add a contact to your roster, with a status of none, no subscriptions.
- AddContact(JID, oneshot::Sender<Result<(), RosterError>>),
+ AddContact(BareJID, oneshot::Sender<Result<(), RosterError>>),
/// 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, oneshot::Sender<Result<(), SubscribeError>>),
+ BuddyRequest(BareJID, oneshot::Sender<Result<(), SubscribeError>>),
/// send a subscription request, without pre-approval. if not already added to roster server adds to roster.
- SubscriptionRequest(JID, oneshot::Sender<Result<(), SubscribeError>>),
+ SubscriptionRequest(BareJID, oneshot::Sender<Result<(), SubscribeError>>),
/// accept a friend request by accepting a pending subscription and sending a subscription request back. if not already added to roster adds to roster.
- AcceptBuddyRequest(JID, oneshot::Sender<Result<(), SubscribeError>>),
+ AcceptBuddyRequest(BareJID, oneshot::Sender<Result<(), SubscribeError>>),
/// accept a pending subscription and doesn't send a subscription request back. if not already added to roster adds to roster.
- AcceptSubscriptionRequest(JID, oneshot::Sender<Result<(), SubscribeError>>),
+ AcceptSubscriptionRequest(BareJID, oneshot::Sender<Result<(), SubscribeError>>),
/// unsubscribe to a contact, but don't remove their subscription.
- UnsubscribeFromContact(JID, oneshot::Sender<Result<(), WriteError>>),
+ UnsubscribeFromContact(BareJID, oneshot::Sender<Result<(), WriteError>>),
/// stop a contact from being subscribed, but stay subscribed to the contact.
- UnsubscribeContact(JID, oneshot::Sender<Result<(), WriteError>>),
+ UnsubscribeContact(BareJID, oneshot::Sender<Result<(), WriteError>>),
/// remove subscriptions to and from contact, but keep in roster.
- UnfriendContact(JID, oneshot::Sender<Result<(), WriteError>>),
+ UnfriendContact(BareJID, oneshot::Sender<Result<(), WriteError>>),
/// remove a contact from the contact list. will remove subscriptions if not already done then delete contact from roster.
- DeleteContact(JID, oneshot::Sender<Result<(), RosterError>>),
+ DeleteContact(BareJID, oneshot::Sender<Result<(), RosterError>>),
/// update contact. contact details will be overwritten with the contents of the contactupdate struct.
- UpdateContact(JID, ContactUpdate, oneshot::Sender<Result<(), RosterError>>),
+ UpdateContact(
+ BareJID,
+ ContactUpdate,
+ oneshot::Sender<Result<(), RosterError>>,
+ ),
/// set online status. if disconnected, will be cached so when client connects, will be sent as the initial presence.
SetStatus(Online, oneshot::Sender<Result<(), StatusError>>),
/// send presence stanza
@@ -120,7 +134,7 @@ pub enum Command<Fs: FileStore> {
// TODO: should probably make it so people can add non-contact auto presence sharing in the client (most likely through setting an internal setting)
/// 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),
+ SendMessage(BareJID, Body),
// TODO: resend failed messages
// ResendMessage(Uuid),
/// disco info query
@@ -146,7 +160,7 @@ pub enum Command<Fs: FileStore> {
sender: oneshot::Sender<Result<(), PEPError>>,
},
GetPEPItem {
- jid: Option<JID>,
+ jid: Option<BareJID>,
node: String,
id: String,
sender: oneshot::Sender<Result<pep::Item, PEPError>>,
@@ -171,7 +185,7 @@ pub enum UpdateMessage {
Offline(Offline),
/// (only update app roster state, don't replace)
RosterUpdate(Contact, User),
- RosterDelete(JID),
+ RosterDelete(BareJID),
/// presences should be stored with users in the ui, not contacts, as presences can be received from anyone
Presence {
from: JID,
@@ -180,22 +194,23 @@ pub enum UpdateMessage {
// TODO: receipts
// MessageDispatched(Uuid),
Message {
- to: JID,
+ // TODO: rename to chat?
+ to: BareJID,
from: User,
message: Message,
},
MessageDelivery {
id: Uuid,
- chat: JID,
+ chat: BareJID,
delivery: Delivery,
},
- SubscriptionRequest(jid::JID),
+ SubscriptionRequest(BareJID),
NickChanged {
- jid: JID,
+ jid: BareJID,
nick: Option<String>,
},
AvatarChanged {
- jid: JID,
+ jid: BareJID,
id: Option<String>,
},
}
@@ -259,7 +274,7 @@ impl<Fs: FileStore + Clone + Send + Sync + 'static> Client<Fs> {
timeout: Duration::from_secs(20),
};
- let logic = ClientLogic::new(client.clone(), jid.as_bare(), db, update_send, file_store);
+ let logic = ClientLogic::new(client.clone(), jid.to_bare(), db, update_send, file_store);
let actor: CoreClient<ClientLogic<Fs>> =
CoreClient::new(jid, password, command_receiver, None, sup_recv, logic);
@@ -373,7 +388,7 @@ impl<Fs: FileStore> Client<Fs> {
Ok(chats)
}
- pub async fn get_chat(&self, jid: JID) -> Result<Chat, CommandError<DatabaseError>> {
+ pub async fn get_chat(&self, jid: BareJID) -> Result<Chat, CommandError<DatabaseError>> {
let (send, recv) = oneshot::channel();
self.send(CoreClientCommand::Command(Command::GetChat(jid, send)))
.await
@@ -385,12 +400,17 @@ impl<Fs: FileStore> Client<Fs> {
Ok(chat)
}
- pub async fn get_chat_and_user(&self, jid: JID) -> Result<(Chat, User), CommandError<DatabaseError>> {
+ pub async fn get_chat_and_user(
+ &self,
+ jid: BareJID,
+ ) -> Result<(Chat, User), CommandError<DatabaseError>> {
let (send, recv) = oneshot::channel();
- self.send(CoreClientCommand::Command(Command::GetChatAndUser(jid, send)))
- .await
- .map_err(|e| CommandError::Actor(Into::<ActorError>::into(e)))?;
- let result= timeout(self.timeout, recv)
+ self.send(CoreClientCommand::Command(Command::GetChatAndUser(
+ jid, send,
+ )))
+ .await
+ .map_err(|e| CommandError::Actor(Into::<ActorError>::into(e)))?;
+ let result = timeout(self.timeout, recv)
.await
.map_err(|e| CommandError::Actor(Into::<ActorError>::into(e)))?
.map_err(|e| CommandError::Actor(Into::<ActorError>::into(e)))??;
@@ -411,7 +431,7 @@ impl<Fs: FileStore> Client<Fs> {
pub async fn get_messages(
&self,
- jid: JID,
+ jid: BareJID,
) -> Result<Vec<Message>, CommandError<DatabaseError>> {
let (send, recv) = oneshot::channel();
self.send(CoreClientCommand::Command(Command::GetMessages(jid, send)))
@@ -426,7 +446,7 @@ impl<Fs: FileStore> Client<Fs> {
pub async fn get_messages_with_users(
&self,
- jid: JID,
+ jid: BareJID,
) -> Result<Vec<(Message, User)>, CommandError<DatabaseError>> {
let (send, recv) = oneshot::channel();
self.send(CoreClientCommand::Command(Command::GetMessagesWithUsers(
@@ -441,7 +461,7 @@ impl<Fs: FileStore> Client<Fs> {
Ok(messages)
}
- pub async fn delete_chat(&self, jid: JID) -> Result<(), CommandError<DatabaseError>> {
+ pub async fn delete_chat(&self, jid: BareJID) -> Result<(), CommandError<DatabaseError>> {
let (send, recv) = oneshot::channel();
self.send(CoreClientCommand::Command(Command::DeleteChat(jid, send)))
.await
@@ -465,7 +485,7 @@ impl<Fs: FileStore> Client<Fs> {
Ok(result)
}
- pub async fn get_user(&self, jid: JID) -> Result<User, CommandError<DatabaseError>> {
+ pub async fn get_user(&self, jid: BareJID) -> Result<User, CommandError<DatabaseError>> {
let (send, recv) = oneshot::channel();
self.send(CoreClientCommand::Command(Command::GetUser(jid, send)))
.await
@@ -477,7 +497,7 @@ impl<Fs: FileStore> Client<Fs> {
Ok(result)
}
- pub async fn add_contact(&self, jid: JID) -> Result<(), CommandError<RosterError>> {
+ pub async fn add_contact(&self, jid: BareJID) -> Result<(), CommandError<RosterError>> {
let (send, recv) = oneshot::channel();
self.send(CoreClientCommand::Command(Command::AddContact(jid, send)))
.await
@@ -489,7 +509,7 @@ impl<Fs: FileStore> Client<Fs> {
Ok(result)
}
- pub async fn buddy_request(&self, jid: JID) -> Result<(), CommandError<SubscribeError>> {
+ pub async fn buddy_request(&self, jid: BareJID) -> Result<(), CommandError<SubscribeError>> {
let (send, recv) = oneshot::channel();
self.send(CoreClientCommand::Command(Command::BuddyRequest(jid, send)))
.await
@@ -501,7 +521,10 @@ impl<Fs: FileStore> Client<Fs> {
Ok(result)
}
- pub async fn subscription_request(&self, jid: JID) -> Result<(), CommandError<SubscribeError>> {
+ pub async fn subscription_request(
+ &self,
+ jid: BareJID,
+ ) -> Result<(), CommandError<SubscribeError>> {
let (send, recv) = oneshot::channel();
self.send(CoreClientCommand::Command(Command::SubscriptionRequest(
jid, send,
@@ -515,7 +538,10 @@ impl<Fs: FileStore> Client<Fs> {
Ok(result)
}
- pub async fn accept_buddy_request(&self, jid: JID) -> Result<(), CommandError<SubscribeError>> {
+ pub async fn accept_buddy_request(
+ &self,
+ jid: BareJID,
+ ) -> Result<(), CommandError<SubscribeError>> {
let (send, recv) = oneshot::channel();
self.send(CoreClientCommand::Command(Command::AcceptBuddyRequest(
jid, send,
@@ -531,7 +557,7 @@ impl<Fs: FileStore> Client<Fs> {
pub async fn accept_subscription_request(
&self,
- jid: JID,
+ jid: BareJID,
) -> Result<(), CommandError<SubscribeError>> {
let (send, recv) = oneshot::channel();
self.send(CoreClientCommand::Command(
@@ -546,7 +572,10 @@ impl<Fs: FileStore> Client<Fs> {
Ok(result)
}
- pub async fn unsubscribe_from_contact(&self, jid: JID) -> Result<(), CommandError<WriteError>> {
+ pub async fn unsubscribe_from_contact(
+ &self,
+ jid: BareJID,
+ ) -> Result<(), CommandError<WriteError>> {
let (send, recv) = oneshot::channel();
self.send(CoreClientCommand::Command(Command::UnsubscribeFromContact(
jid, send,
@@ -560,7 +589,7 @@ impl<Fs: FileStore> Client<Fs> {
Ok(result)
}
- pub async fn unsubscribe_contact(&self, jid: JID) -> Result<(), CommandError<WriteError>> {
+ pub async fn unsubscribe_contact(&self, jid: BareJID) -> Result<(), CommandError<WriteError>> {
let (send, recv) = oneshot::channel();
self.send(CoreClientCommand::Command(Command::UnsubscribeContact(
jid, send,
@@ -574,7 +603,7 @@ impl<Fs: FileStore> Client<Fs> {
Ok(result)
}
- pub async fn unfriend_contact(&self, jid: JID) -> Result<(), CommandError<WriteError>> {
+ pub async fn unfriend_contact(&self, jid: BareJID) -> Result<(), CommandError<WriteError>> {
let (send, recv) = oneshot::channel();
self.send(CoreClientCommand::Command(Command::UnfriendContact(
jid, send,
@@ -588,7 +617,7 @@ impl<Fs: FileStore> Client<Fs> {
Ok(result)
}
- pub async fn delete_contact(&self, jid: JID) -> Result<(), CommandError<RosterError>> {
+ pub async fn delete_contact(&self, jid: BareJID) -> Result<(), CommandError<RosterError>> {
let (send, recv) = oneshot::channel();
self.send(CoreClientCommand::Command(Command::DeleteContact(
jid, send,
@@ -604,7 +633,7 @@ impl<Fs: FileStore> Client<Fs> {
pub async fn update_contact(
&self,
- jid: JID,
+ jid: BareJID,
update: ContactUpdate,
) -> Result<(), CommandError<RosterError>> {
let (send, recv) = oneshot::channel();
@@ -632,7 +661,7 @@ impl<Fs: FileStore> Client<Fs> {
Ok(result)
}
- pub async fn send_message(&self, jid: JID, body: Body) -> Result<(), ActorError> {
+ pub async fn send_message(&self, jid: BareJID, body: Body) -> Result<(), ActorError> {
self.send(CoreClientCommand::Command(Command::SendMessage(jid, body)))
.await?;
Ok(())
@@ -711,7 +740,8 @@ impl<Fs: FileStore> Client<Fs> {
pub async fn get_pep_item(
&self,
- jid: Option<JID>,
+ // i think this is correct?, should not be able to send pep requests to a full jid.
+ jid: Option<BareJID>,
node: String,
id: String,
) -> Result<pep::Item, CommandError<PEPError>> {
@@ -777,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(
@@ -811,7 +840,7 @@ mod tests {
info!("sending message");
client
.send_message(
- JID::from_str("cel@blos.sm").unwrap(),
+ BareJID::from_str("cel@blos.sm").unwrap(),
chat::Body {
body: "hallo!!!".to_string(),
},
@@ -821,7 +850,7 @@ mod tests {
info!("sent message");
client
.send_message(
- JID::from_str("cel@blos.sm").unwrap(),
+ BareJID::from_str("cel@blos.sm").unwrap(),
chat::Body {
body: "hallo 2".to_string(),
},
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 9d61ca4..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;
@@ -11,7 +15,7 @@ use crate::{
use super::ClientLogic;
-pub async fn handle_connect<Fs: FileStore + Clone + Send + Sync>(
+pub async fn handle_connect<Fs: FileStore + Clone + Send + Sync + 'static>(
logic: ClientLogic<Fs>,
connection: Connected,
) {
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 f5705f4..5dc8793 100644
--- a/filamento/src/logic/local_only.rs
+++ b/filamento/src/logic/local_only.rs
@@ -1,4 +1,8 @@
-use jid::JID;
+// SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+use jid::{BareJID, JID};
use uuid::Uuid;
use crate::{
@@ -39,14 +43,14 @@ pub async fn handle_get_chats_ordered_with_latest_messages_and_users<Fs: FileSto
pub async fn handle_get_chat<Fs: FileStore + Clone>(
logic: &ClientLogic<Fs>,
- jid: JID,
+ jid: BareJID,
) -> Result<Chat, DatabaseError> {
Ok(logic.db().read_chat(jid).await?)
}
pub async fn handle_get_chat_and_user<Fs: FileStore + Clone>(
logic: &ClientLogic<Fs>,
- jid: JID,
+ jid: BareJID,
) -> Result<(Chat, User), DatabaseError> {
Ok(logic.db().read_chat_and_user(jid).await?)
}
@@ -60,21 +64,21 @@ pub async fn handle_get_message<Fs: FileStore + Clone>(
pub async fn handle_get_messages<Fs: FileStore + Clone>(
logic: &ClientLogic<Fs>,
- jid: JID,
+ jid: BareJID,
) -> Result<Vec<Message>, DatabaseError> {
Ok(logic.db().read_message_history(jid).await?)
}
pub async fn handle_get_messages_with_users<Fs: FileStore + Clone>(
logic: &ClientLogic<Fs>,
- jid: JID,
+ jid: BareJID,
) -> Result<Vec<(Message, User)>, DatabaseError> {
Ok(logic.db().read_message_history_with_users(jid).await?)
}
pub async fn handle_delete_chat<Fs: FileStore + Clone>(
logic: &ClientLogic<Fs>,
- jid: JID,
+ jid: BareJID,
) -> Result<(), DatabaseError> {
Ok(logic.db().delete_chat(jid).await?)
}
@@ -88,7 +92,7 @@ pub async fn handle_delete_messaage<Fs: FileStore + Clone>(
pub async fn handle_get_user<Fs: FileStore + Clone>(
logic: &ClientLogic<Fs>,
- jid: JID,
+ jid: BareJID,
) -> Result<User, DatabaseError> {
Ok(logic.db().read_user(jid).await?)
}
diff --git a/filamento/src/logic/mod.rs b/filamento/src/logic/mod.rs
index 5e05dac..7ae0235 100644
--- a/filamento/src/logic/mod.rs
+++ b/filamento/src/logic/mod.rs
@@ -1,6 +1,10 @@
+// SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
use std::{collections::HashMap, sync::Arc};
-use jid::JID;
+use jid::{BareJID, JID};
use lampada::{Connected, Logic, error::ReadError};
use stanza::client::Stanza;
use tokio::sync::{Mutex, mpsc, oneshot};
@@ -25,7 +29,7 @@ mod process_stanza;
#[derive(Clone)]
pub struct ClientLogic<Fs: FileStore> {
client: Client<Fs>,
- bare_jid: JID,
+ jid: BareJID,
db: Db,
pending: Pending,
update_sender: mpsc::Sender<UpdateMessage>,
@@ -80,7 +84,7 @@ impl Pending {
impl<Fs: FileStore> ClientLogic<Fs> {
pub fn new(
client: Client<Fs>,
- bare_jid: JID,
+ jid: BareJID,
db: Db,
update_sender: mpsc::Sender<UpdateMessage>,
file_store: Fs,
@@ -90,7 +94,7 @@ impl<Fs: FileStore> ClientLogic<Fs> {
pending: Pending::new(),
update_sender,
client,
- bare_jid,
+ jid,
file_store,
}
}
@@ -127,7 +131,7 @@ impl<Fs: FileStore> ClientLogic<Fs> {
}
}
-impl<Fs: FileStore + Clone + Send + Sync> Logic for ClientLogic<Fs> {
+impl<Fs: FileStore + Clone + Send + Sync + 'static> Logic for ClientLogic<Fs> {
type Cmd = Command<Fs>;
// pub async fn handle_stream_error(self, error) {}
diff --git a/filamento/src/logic/offline.rs b/filamento/src/logic/offline.rs
index 606b04f..1d79f86 100644
--- a/filamento/src/logic/offline.rs
+++ b/filamento/src/logic/offline.rs
@@ -1,6 +1,11 @@
+// SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
use std::process::id;
use chrono::Utc;
+use jid::FullJID;
use lampada::error::WriteError;
use tracing::error;
use uuid::Uuid;
@@ -19,9 +24,13 @@ use crate::{
};
use super::{
+ ClientLogic,
local_only::{
- handle_delete_chat, handle_delete_messaage, handle_get_chat, handle_get_chat_and_user, handle_get_chats, handle_get_chats_ordered, handle_get_chats_ordered_with_latest_messages, handle_get_chats_ordered_with_latest_messages_and_users, handle_get_message, handle_get_messages, handle_get_messages_with_users, handle_get_user
- }, ClientLogic
+ handle_delete_chat, handle_delete_messaage, handle_get_chat, handle_get_chat_and_user,
+ handle_get_chats, handle_get_chats_ordered, handle_get_chats_ordered_with_latest_messages,
+ handle_get_chats_ordered_with_latest_messages_and_users, handle_get_message,
+ handle_get_messages, handle_get_messages_with_users, handle_get_user,
+ },
};
pub async fn handle_offline<Fs: FileStore + Clone>(logic: ClientLogic<Fs>, command: Command<Fs>) {
@@ -153,22 +162,19 @@ pub async fn handle_offline_result<Fs: FileStore + Clone>(
let message = Message {
id,
- from: logic.bare_jid.clone(),
+ from: logic.jid.clone(),
// TODO: failure reason
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()
- .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
- logic.bare_jid.clone(),
- )
+ // TODO: can create message without a resource....
+ .create_message(message.clone(), jid.clone(), logic.jid.clone())
.await
{
// TODO: should these really be handle_error or just the error macro?
@@ -177,12 +183,12 @@ pub async fn handle_offline_result<Fs: FileStore + Clone>(
.await;
}
- let from = match logic.db().read_user(logic.bare_jid.clone()).await {
+ let from = match logic.db().read_user(logic.jid.clone()).await {
Ok(u) => u,
Err(e) => {
error!("{}", e);
User {
- jid: logic.bare_jid.clone(),
+ jid: logic.jid.clone(),
nick: None,
avatar: None,
}
@@ -192,7 +198,7 @@ pub async fn handle_offline_result<Fs: FileStore + Clone>(
logic
.update_sender()
.send(crate::UpdateMessage::Message {
- to: jid.as_bare(),
+ to: jid,
message,
from,
})
diff --git a/filamento/src/logic/online.rs b/filamento/src/logic/online.rs
index 9814ff2..d49a844 100644
--- a/filamento/src/logic/online.rs
+++ b/filamento/src/logic/online.rs
@@ -1,9 +1,13 @@
+// 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};
use chrono::Utc;
use image::ImageReader;
-use jid::JID;
+use jid::{BareJID, JID};
use lampada::{Connected, WriteMessage, error::WriteError};
use sha1::{Digest, Sha1};
use stanza::{
@@ -11,7 +15,9 @@ use stanza::{
iq::{self, Iq, IqType, Query}, Stanza
}, xep_0030::{info, items}, xep_0060::{self, owner, pubsub::{self, Pubsub}}, xep_0084, xep_0172::{self, Nick}, xep_0203::Delay
};
-use tokio::sync::oneshot;
+use tokio::{sync::oneshot, task::spawn_blocking};
+#[cfg(target_arch = "wasm32")]
+use tokio_with_wasm::alias as tokio;
use tracing::{debug, error, info};
use uuid::Uuid;
@@ -27,7 +33,7 @@ use super::{
}, ClientLogic
};
-pub async fn handle_online<Fs: FileStore + Clone>(logic: ClientLogic<Fs>, command: Command<Fs>, connection: Connected) {
+pub async fn handle_online<Fs: FileStore + Clone + 'static>(logic: ClientLogic<Fs>, command: Command<Fs>, connection: Connected) {
let result = handle_online_result(&logic, command, connection).await;
match result {
Ok(_) => {}
@@ -41,7 +47,7 @@ pub async fn handle_get_roster<Fs: FileStore + Clone>(
) -> Result<Vec<Contact>, RosterError> {
let iq_id = Uuid::new_v4().to_string();
let stanza = Stanza::Iq(Iq {
- from: Some(connection.jid().clone()),
+ from: Some(connection.jid().clone().into()),
id: iq_id.to_string(),
to: None,
r#type: IqType::Get,
@@ -101,7 +107,7 @@ pub async fn handle_get_roster_with_users<Fs: FileStore + Clone>(
) -> Result<Vec<(Contact, User)>, RosterError> {
let iq_id = Uuid::new_v4().to_string();
let stanza = Stanza::Iq(Iq {
- from: Some(connection.jid().clone()),
+ from: Some(connection.jid().clone().into()),
id: iq_id.to_string(),
to: None,
r#type: IqType::Get,
@@ -162,11 +168,11 @@ pub async fn handle_get_roster_with_users<Fs: FileStore + Clone>(
pub async fn handle_add_contact<Fs: FileStore + Clone>(
logic: &ClientLogic<Fs>,
connection: Connected,
- jid: JID,
+ jid: BareJID,
) -> Result<(), RosterError> {
let iq_id = Uuid::new_v4().to_string();
let set_stanza = Stanza::Iq(Iq {
- from: Some(connection.jid().clone()),
+ from: Some(connection.jid().clone().into()),
id: iq_id.clone(),
to: None,
r#type: IqType::Set,
@@ -220,9 +226,10 @@ pub async fn handle_add_contact<Fs: FileStore + Clone>(
pub async fn handle_buddy_request<Fs: FileStore + Clone>(
logic: &ClientLogic<Fs>,
connection: Connected,
- jid: JID,
+ jid: BareJID,
) -> Result<(), SubscribeError> {
- let client_user = logic.db.read_user(logic.bare_jid.clone()).await?;
+ let jid: JID = jid.into();
+ let client_user = logic.db.read_user(logic.jid.clone()).await?;
let nick = client_user.nick.map(|nick| Nick(nick));
let presence = Stanza::Presence(stanza::client::presence::Presence {
to: Some(jid.clone()),
@@ -243,13 +250,13 @@ pub async fn handle_buddy_request<Fs: FileStore + Clone>(
pub async fn handle_subscription_request<Fs: FileStore + Clone>(
logic: &ClientLogic<Fs>,
connection: Connected,
- jid: JID,
+ jid: BareJID,
) -> Result<(), SubscribeError> {
// TODO: i should probably have builders
- let client_user = logic.db.read_user(logic.bare_jid.clone()).await?;
+ let client_user = logic.db.read_user(logic.jid.clone()).await?;
let nick = client_user.nick.map(|nick| Nick(nick));
let presence = Stanza::Presence(stanza::client::presence::Presence {
- to: Some(jid),
+ to: Some(jid.into()),
r#type: Some(stanza::client::presence::PresenceType::Subscribe),
nick,
..Default::default()
@@ -261,15 +268,16 @@ pub async fn handle_subscription_request<Fs: FileStore + Clone>(
pub async fn handle_accept_buddy_request<Fs: FileStore + Clone>(
logic: &ClientLogic<Fs>,
connection: Connected,
- jid: JID,
+ jid: BareJID,
) -> Result<(), SubscribeError> {
+ let jid: JID = jid.into();
let presence = Stanza::Presence(stanza::client::presence::Presence {
to: Some(jid.clone()),
r#type: Some(stanza::client::presence::PresenceType::Subscribed),
..Default::default()
});
connection.write_handle().write(presence).await?;
- let client_user = logic.db.read_user(logic.bare_jid.clone()).await?;
+ let client_user = logic.db.read_user(logic.jid.clone()).await?;
let nick = client_user.nick.map(|nick| Nick(nick));
let presence = Stanza::Presence(stanza::client::presence::Presence {
to: Some(jid),
@@ -284,14 +292,11 @@ pub async fn handle_accept_buddy_request<Fs: FileStore + Clone>(
pub async fn handle_accept_subscription_request<Fs: FileStore + Clone>(
logic: &ClientLogic<Fs>,
connection: Connected,
- jid: JID,
+ jid: BareJID,
) -> Result<(), SubscribeError> {
- let client_user = logic.db.read_user(logic.bare_jid.clone()).await?;
- let nick = client_user.nick.map(|nick| Nick(nick));
let presence = Stanza::Presence(stanza::client::presence::Presence {
- to: Some(jid),
- r#type: Some(stanza::client::presence::PresenceType::Subscribe),
- nick,
+ to: Some(jid.into()),
+ r#type: Some(stanza::client::presence::PresenceType::Subscribed),
..Default::default()
});
connection.write_handle().write(presence).await?;
@@ -300,10 +305,10 @@ pub async fn handle_accept_subscription_request<Fs: FileStore + Clone>(
pub async fn handle_unsubscribe_from_contact(
connection: Connected,
- jid: JID,
+ jid: BareJID,
) -> Result<(), WriteError> {
let presence = Stanza::Presence(stanza::client::presence::Presence {
- to: Some(jid),
+ to: Some(jid.into()),
r#type: Some(stanza::client::presence::PresenceType::Unsubscribe),
..Default::default()
});
@@ -311,9 +316,9 @@ pub async fn handle_unsubscribe_from_contact(
Ok(())
}
-pub async fn handle_unsubscribe_contact(connection: Connected, jid: JID) -> Result<(), WriteError> {
+pub async fn handle_unsubscribe_contact(connection: Connected, jid: BareJID) -> Result<(), WriteError> {
let presence = Stanza::Presence(stanza::client::presence::Presence {
- to: Some(jid),
+ to: Some(jid.into()),
r#type: Some(stanza::client::presence::PresenceType::Unsubscribed),
..Default::default()
});
@@ -321,7 +326,8 @@ pub async fn handle_unsubscribe_contact(connection: Connected, jid: JID) -> Resu
Ok(())
}
-pub async fn handle_unfriend_contact(connection: Connected, jid: JID) -> Result<(), WriteError> {
+pub async fn handle_unfriend_contact(connection: Connected, jid: BareJID) -> Result<(), WriteError> {
+ let jid: JID = jid.into();
let presence = Stanza::Presence(stanza::client::presence::Presence {
to: Some(jid.clone()),
r#type: Some(stanza::client::presence::PresenceType::Unsubscribe),
@@ -340,11 +346,11 @@ pub async fn handle_unfriend_contact(connection: Connected, jid: JID) -> Result<
pub async fn handle_delete_contact<Fs: FileStore + Clone>(
logic: &ClientLogic<Fs>,
connection: Connected,
- jid: JID,
+ jid: BareJID,
) -> Result<(), RosterError> {
let iq_id = Uuid::new_v4().to_string();
let set_stanza = Stanza::Iq(Iq {
- from: Some(connection.jid().clone()),
+ from: Some(connection.jid().clone().into()),
id: iq_id.clone(),
to: None,
r#type: IqType::Set,
@@ -399,7 +405,7 @@ pub async fn handle_delete_contact<Fs: FileStore + Clone>(
pub async fn handle_update_contact<Fs: FileStore + Clone>(
logic: &ClientLogic<Fs>,
connection: Connected,
- jid: JID,
+ jid: BareJID,
contact_update: ContactUpdate,
) -> Result<(), RosterError> {
let iq_id = Uuid::new_v4().to_string();
@@ -410,7 +416,8 @@ pub async fn handle_update_contact<Fs: FileStore + Clone>(
.map(|group| stanza::roster::Group(Some(group))),
);
let set_stanza = Stanza::Iq(Iq {
- from: Some(connection.jid().clone()),
+ // TODO: these clones could technically be avoided?
+ from: Some(connection.jid().clone().into()),
id: iq_id.clone(),
to: None,
r#type: IqType::Set,
@@ -474,7 +481,7 @@ pub async fn handle_set_status<Fs: FileStore + Clone>(
Ok(())
}
-pub async fn handle_send_message<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>, connection: Connected, jid: JID, body: Body) {
+pub async fn handle_send_message<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>, connection: Connected, jid: BareJID, body: Body) {
// upsert the chat and user the message will be delivered to. if there is a conflict, it will return whatever was there, otherwise it will return false by default.
// let have_chatted = logic.db().upsert_chat_and_user(&jid).await.unwrap_or(false);
let have_chatted = match logic.db().upsert_chat_and_user(jid.clone()).await {
@@ -490,7 +497,7 @@ pub async fn handle_send_message<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>,
let nick;
let mark_chat_as_chatted;
if have_chatted == false {
- match logic.db.read_user(logic.bare_jid.clone()).await {
+ match logic.db.read_user(logic.jid.clone()).await {
Ok(u) => {
nick = u.nick.map(|nick| Nick(nick));
mark_chat_as_chatted = true;
@@ -513,17 +520,19 @@ pub async fn handle_send_message<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>,
let timestamp = Utc::now();
let message = Message {
id,
- from: connection.jid().as_bare(),
+ from: connection.jid().to_bare(),
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?
@@ -532,12 +541,12 @@ pub async fn handle_send_message<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>,
.await;
}
- let from = match logic.db().read_user(logic.bare_jid.clone()).await {
+ let from = match logic.db().read_user(logic.jid.clone()).await {
Ok(u) => u,
Err(e) => {
error!("{}", e);
User {
- jid: logic.bare_jid.clone(),
+ jid: logic.jid.clone(),
nick: None,
avatar: None,
}
@@ -548,7 +557,7 @@ pub async fn handle_send_message<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>,
logic
.update_sender()
.send(UpdateMessage::Message {
- to: jid.as_bare(),
+ to: jid.clone(),
message,
from,
})
@@ -556,9 +565,9 @@ pub async fn handle_send_message<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>,
// prepare the message stanza
let message_stanza = Stanza::Message(stanza::client::message::Message {
- from: Some(connection.jid().clone()),
+ from: Some(connection.jid().clone().into()),
id: Some(id.to_string()),
- to: Some(jid.clone()),
+ to: Some(jid.clone().into()),
// TODO: specify message type
r#type: stanza::client::message::MessageType::Chat,
// TODO: lang ?
@@ -583,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);
}
@@ -639,7 +649,7 @@ pub async fn handle_disco_info<Fs: FileStore + Clone>(
) -> Result<Info, DiscoError> {
let id = Uuid::new_v4().to_string();
let request = Iq {
- from: Some(connection.jid().clone()),
+ from: Some(connection.jid().clone().into()),
id: id.clone(),
to: jid.clone(),
r#type: IqType::Get,
@@ -667,7 +677,7 @@ pub async fn handle_disco_info<Fs: FileStore + Clone>(
}) if r#type == IqType::Result || r#type == IqType::Error => {
if from == jid || {
if jid == None {
- from == Some(connection.jid().as_bare())
+ from == Some(connection.jid().to_bare().into())
} else {
false
}
@@ -694,7 +704,7 @@ pub async fn handle_disco_info<Fs: FileStore + Clone>(
}
} else {
Err(DiscoError::IncorrectEntity(
- from.unwrap_or_else(|| connection.jid().as_bare()),
+ from.unwrap_or_else(|| connection.jid().to_bare().into()),
))
}
}
@@ -710,7 +720,7 @@ pub async fn handle_disco_items<Fs: FileStore + Clone>(
) -> Result<Items, DiscoError> {
let id = Uuid::new_v4().to_string();
let request = Iq {
- from: Some(connection.jid().clone()),
+ from: Some(connection.jid().clone().into()),
id: id.clone(),
to: jid.clone(),
r#type: IqType::Get,
@@ -736,7 +746,7 @@ pub async fn handle_disco_items<Fs: FileStore + Clone>(
}) if r#type == IqType::Result || r#type == IqType::Error => {
if from == jid || {
if jid == None {
- from == Some(connection.jid().as_bare())
+ from == Some(connection.jid().to_bare().into())
} else {
false
}
@@ -763,7 +773,7 @@ pub async fn handle_disco_items<Fs: FileStore + Clone>(
}
} else {
Err(DiscoError::IncorrectEntity(
- from.unwrap_or_else(|| connection.jid().as_bare()),
+ from.unwrap_or_else(|| connection.jid().to_bare().into()),
))
}
}
@@ -828,7 +838,7 @@ pub async fn handle_publish_pep_item<Fs: FileStore + Clone>(
},
};
let request = Iq {
- from: Some(connection.jid().clone()),
+ from: Some(connection.jid().clone().into()),
id: id.clone(),
to: None,
r#type: IqType::Set,
@@ -850,7 +860,7 @@ pub async fn handle_publish_pep_item<Fs: FileStore + Clone>(
// TODO: maybe abstract a bunch of these different errors related to iqs into an iq error thing? as in like call iq.result(), get the query from inside, error otherwise.
}) if r#type == IqType::Result || r#type == IqType::Error => {
if from == None ||
- from == Some(connection.jid().as_bare())
+ from == Some(connection.jid().to_bare().into())
{
match r#type {
IqType::Result => {
@@ -870,7 +880,7 @@ pub async fn handle_publish_pep_item<Fs: FileStore + Clone>(
}
} else {
Err(PEPError::IncorrectEntity(
- from.unwrap_or_else(|| connection.jid().as_bare()),
+ from.unwrap_or_else(|| connection.jid().to_bare().into()),
))
}
}
@@ -878,10 +888,11 @@ pub async fn handle_publish_pep_item<Fs: FileStore + Clone>(
}
}
-pub async fn handle_get_pep_item<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>, connection: Connected, jid: Option<JID>, node: String, id: String) -> Result<pep::Item, PEPError> {
+pub async fn handle_get_pep_item<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>, connection: Connected, jid: Option<BareJID>, node: String, id: String) -> Result<pep::Item, PEPError> {
+ let jid = jid.map(|jid| Into::<JID>::into(jid));
let stanza_id = Uuid::new_v4().to_string();
let request = Iq {
- from: Some(connection.jid().clone()),
+ from: Some(connection.jid().clone().into()),
id: stanza_id.clone(),
to: jid.clone(),
r#type: IqType::Get,
@@ -909,7 +920,7 @@ pub async fn handle_get_pep_item<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>,
}) if r#type == IqType::Result || r#type == IqType::Error => {
if from == jid || {
if jid == None {
- from == Some(connection.jid().as_bare())
+ from == Some(connection.jid().to_bare().into())
} else {
false
}
@@ -955,7 +966,7 @@ pub async fn handle_get_pep_item<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>,
} else {
// TODO: include expected entity
Err(PEPError::IncorrectEntity(
- from.unwrap_or_else(|| connection.jid().as_bare()),
+ from.unwrap_or_else(|| connection.jid().to_bare().into()),
))
}
}
@@ -968,29 +979,33 @@ pub async fn handle_change_nick<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>,
Ok(())
}
-pub async fn handle_change_avatar<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>, img_data: Option<Vec<u8>>) -> Result<(), AvatarPublishError<Fs>> {
+pub async fn handle_change_avatar<Fs: FileStore + Clone + 'static>(logic: &ClientLogic<Fs>, img_data: Option<Vec<u8>>) -> Result<(), AvatarPublishError<Fs>> {
match img_data {
// set avatar
Some(data) => {
- // load the image data and guess the format
- let image = ImageReader::new(Cursor::new(data)).with_guessed_format()?.decode()?;
+ let (bytes, hash, data_png, data_b64) = spawn_blocking(move || -> Result<_, _> {
+ // load the image data and guess the format
+ let image = ImageReader::new(Cursor::new(data)).with_guessed_format()?.decode()?;
+
+ // convert the image to png;
+ let mut data_png = Vec::new();
+ let image = image.resize(192, 192, image::imageops::FilterType::Nearest);
+ image.write_to(&mut Cursor::new(&mut data_png), image::ImageFormat::Jpeg)?;
- // convert the image to png;
- let mut data_png = Vec::new();
- let image = image.resize(192, 192, image::imageops::FilterType::Nearest);
- image.write_to(&mut Cursor::new(&mut data_png), image::ImageFormat::Jpeg)?;
+ // calculate the length of the data in bytes.
+ let bytes = data_png.len().try_into()?;
- // calculate the length of the data in bytes.
- let bytes = data_png.len().try_into()?;
+ // calculate sha1 hash of the data
+ let mut sha1 = Sha1::new();
+ sha1.update(&data_png);
+ let sha1_result = sha1.finalize();
+ let hash = hex::encode(sha1_result);
- // calculate sha1 hash of the data
- let mut sha1 = Sha1::new();
- sha1.update(&data_png);
- let sha1_result = sha1.finalize();
- let hash = hex::encode(sha1_result);
+ // encode the image data as base64
+ let data_b64 = BASE64_STANDARD.encode(data_png.clone());
- // encode the image data as base64
- let data_b64 = BASE64_STANDARD.encode(data_png.clone());
+ Ok::<(u32, String, Vec<u8>, String), AvatarPublishError<Fs>>((bytes, hash, data_png, data_b64))
+ }).await.unwrap()?;
// publish the data to the data node
logic.client().publish(pep::Item::AvatarData(Some(avatar::Data { hash: hash.clone(), data_b64 })), "urn:xmpp:avatar:data".to_string()).await?;
@@ -1024,7 +1039,7 @@ pub async fn handle_delete_pep_node<Fs: FileStore + Clone>(
) -> Result<(), PEPError> {
let id = Uuid::new_v4().to_string();
let request = Iq {
- from: Some(connection.jid().clone()),
+ from: Some(connection.jid().clone().into()),
id: id.clone(),
to: None,
r#type: IqType::Set,
@@ -1046,7 +1061,7 @@ pub async fn handle_delete_pep_node<Fs: FileStore + Clone>(
// TODO: maybe abstract a bunch of these different errors related to iqs into an iq error thing? as in like call iq.result(), get the query from inside, error otherwise.
}) if r#type == IqType::Result || r#type == IqType::Error => {
if from == None ||
- from == Some(connection.jid().as_bare())
+ from == Some(connection.jid().to_bare().into())
{
match r#type {
IqType::Result => {
@@ -1067,7 +1082,7 @@ pub async fn handle_delete_pep_node<Fs: FileStore + Clone>(
}
} else {
Err(PEPError::IncorrectEntity(
- from.unwrap_or_else(|| connection.jid().as_bare()),
+ from.unwrap_or_else(|| connection.jid().to_bare().into()),
))
}
}
@@ -1076,7 +1091,7 @@ pub async fn handle_delete_pep_node<Fs: FileStore + Clone>(
}
// TODO: could probably macro-ise?
-pub async fn handle_online_result<Fs: FileStore + Clone>(
+pub async fn handle_online_result<Fs: FileStore + Clone + 'static>(
logic: &ClientLogic<Fs>,
command: Command<Fs>,
connection: Connected,
diff --git a/filamento/src/logic/process_stanza.rs b/filamento/src/logic/process_stanza.rs
index 30d0830..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};
@@ -70,41 +74,32 @@ pub async fn recv_message<Fs: FileStore + Clone>(
// TODO: proper id xep
.map(|id| Uuid::from_str(&id).unwrap_or_else(|_| Uuid::new_v4()))
.unwrap_or_else(|| Uuid::new_v4()),
- from: from.as_bare(),
+ from: from.to_bare(),
timestamp,
body: Body {
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.clone()).await {
- Ok(_) => {
- if let Err(e) = logic
- .db()
- .create_message_with_user_resource(
- message.clone(),
- from.clone(),
- from.clone(),
- )
- .await
- {
- 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.as_bare()).await {
+ let from_user = match logic.db().read_user(from.to_bare()).await {
Ok(u) => u,
Err(e) => {
error!("{}", e);
User {
- jid: from.as_bare(),
+ jid: from.to_bare(),
nick: None,
avatar: None,
}
@@ -115,7 +110,7 @@ pub async fn recv_message<Fs: FileStore + Clone>(
logic
.update_sender()
.send(UpdateMessage::Message {
- to: from.as_bare(),
+ to: from.to_bare(),
from: from_user,
message,
})
@@ -125,13 +120,13 @@ pub async fn recv_message<Fs: FileStore + Clone>(
if let Some(nick) = stanza_message.nick {
let nick = nick.0;
if nick.is_empty() {
- match logic.db().delete_user_nick(from.as_bare()).await {
+ match logic.db().delete_user_nick(from.to_bare()).await {
Ok(changed) => {
if changed {
logic
.update_sender()
.send(UpdateMessage::NickChanged {
- jid: from.as_bare(),
+ jid: from.to_bare(),
nick: None,
})
.await;
@@ -145,7 +140,7 @@ pub async fn recv_message<Fs: FileStore + Clone>(
logic
.update_sender()
.send(UpdateMessage::NickChanged {
- jid: from.as_bare(),
+ jid: from.to_bare(),
nick: None,
})
.await;
@@ -154,7 +149,7 @@ pub async fn recv_message<Fs: FileStore + Clone>(
} else {
match logic
.db()
- .upsert_user_nick(from.as_bare(), nick.clone())
+ .upsert_user_nick(from.to_bare(), nick.clone())
.await
{
Ok(changed) => {
@@ -162,7 +157,7 @@ pub async fn recv_message<Fs: FileStore + Clone>(
logic
.update_sender()
.send(UpdateMessage::NickChanged {
- jid: from.as_bare(),
+ jid: from.to_bare(),
nick: Some(nick),
})
.await;
@@ -176,7 +171,7 @@ pub async fn recv_message<Fs: FileStore + Clone>(
logic
.update_sender()
.send(UpdateMessage::NickChanged {
- jid: from.as_bare(),
+ jid: from.to_bare(),
nick: Some(nick),
})
.await;
@@ -199,7 +194,7 @@ pub async fn recv_message<Fs: FileStore + Clone>(
if nick.is_empty() {
match logic
.db()
- .delete_user_nick(from.as_bare())
+ .delete_user_nick(from.to_bare())
.await
{
Ok(changed) => {
@@ -207,7 +202,7 @@ pub async fn recv_message<Fs: FileStore + Clone>(
logic
.update_sender()
.send(UpdateMessage::NickChanged {
- jid: from.as_bare(),
+ jid: from.to_bare(),
nick: None,
})
.await;
@@ -223,7 +218,7 @@ pub async fn recv_message<Fs: FileStore + Clone>(
logic
.update_sender()
.send(UpdateMessage::NickChanged {
- jid: from.as_bare(),
+ jid: from.to_bare(),
nick: None,
})
.await;
@@ -233,7 +228,7 @@ pub async fn recv_message<Fs: FileStore + Clone>(
match logic
.db()
.upsert_user_nick(
- from.as_bare(),
+ from.to_bare(),
nick.clone(),
)
.await
@@ -243,7 +238,7 @@ pub async fn recv_message<Fs: FileStore + Clone>(
logic
.update_sender()
.send(UpdateMessage::NickChanged {
- jid: from.as_bare(),
+ jid: from.to_bare(),
nick: Some(nick),
})
.await;
@@ -259,7 +254,7 @@ pub async fn recv_message<Fs: FileStore + Clone>(
logic
.update_sender()
.send(UpdateMessage::NickChanged {
- jid: from.as_bare(),
+ jid: from.to_bare(),
nick: Some(nick),
})
.await;
@@ -294,7 +289,7 @@ pub async fn recv_message<Fs: FileStore + Clone>(
match logic
.db()
.upsert_user_avatar(
- from.as_bare(),
+ from.to_bare(),
metadata.id.clone(),
)
.await
@@ -323,7 +318,7 @@ pub async fn recv_message<Fs: FileStore + Clone>(
}) {
Ok(false) => {
// get data
- let pep_item = logic.client().get_pep_item(Some(from.as_bare()), "urn:xmpp:avatar:data".to_string(), metadata.id.clone()).await.map_err(|err| Into::<AvatarUpdateError<Fs>>::into(err))?;
+ let pep_item = logic.client().get_pep_item(Some(from.to_bare()), "urn:xmpp:avatar:data".to_string(), metadata.id.clone()).await.map_err(|err| Into::<AvatarUpdateError<Fs>>::into(err))?;
match pep_item {
crate::pep::Item::AvatarData(data) => {
let data = data.map(|data| data.data_b64).unwrap_or_default().replace("\n", "");
@@ -344,7 +339,7 @@ pub async fn recv_message<Fs: FileStore + Clone>(
.update_sender()
.send(
UpdateMessage::AvatarChanged {
- jid: from.as_bare(),
+ jid: from.to_bare(),
id: Some(
metadata.id.clone(),
),
@@ -371,7 +366,7 @@ pub async fn recv_message<Fs: FileStore + Clone>(
.update_sender()
.send(
UpdateMessage::AvatarChanged {
- jid: from.as_bare(),
+ jid: from.to_bare(),
id: Some(
metadata.id.clone(),
),
@@ -401,7 +396,7 @@ pub async fn recv_message<Fs: FileStore + Clone>(
// delete avatar
match logic
.db()
- .delete_user_avatar(from.as_bare())
+ .delete_user_avatar(from.to_bare())
.await
{
Ok((changed, old_avatar)) => {
@@ -419,7 +414,7 @@ pub async fn recv_message<Fs: FileStore + Clone>(
.update_sender()
.send(
UpdateMessage::AvatarChanged {
- jid: from.as_bare(),
+ jid: from.to_bare(),
id: None,
},
)
@@ -488,6 +483,7 @@ pub async fn recv_presence(
}
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 from = from.try_into()?;
Ok(Some(UpdateMessage::SubscriptionRequest(from)))
}
stanza::client::presence::PresenceType::Unavailable => {
@@ -546,7 +542,8 @@ pub async fn recv_iq<Fs: FileStore + Clone>(
iq: Iq,
) -> Result<Option<UpdateMessage>, IqProcessError> {
if let Some(to) = &iq.to {
- if *to == *connection.jid() {
+ // TODO: this clone could mayb b avoided
+ if *to == connection.jid().clone().into() {
} else {
return Err(IqProcessError::Iq(IqError::IncorrectAddressee(to.clone())));
}
@@ -556,7 +553,9 @@ pub async fn recv_iq<Fs: FileStore + Clone>(
let from = iq
.from
.clone()
- .unwrap_or_else(|| connection.server().clone());
+ // TODO: maybe actually store the server in the connection again LOL
+ // .unwrap_or_else(|| connection.server().clone());
+ .unwrap_or_else(|| connection.jid().domainpart.parse().unwrap());
let id = iq.id.clone();
debug!("received iq result with id `{}` from {}", id, from);
logic
@@ -570,7 +569,8 @@ pub async fn recv_iq<Fs: FileStore + Clone>(
let from = iq
.from
.clone()
- .unwrap_or_else(|| connection.server().clone());
+ // .unwrap_or_else(|| connection.server().clone());
+ .unwrap_or_else(|| connection.jid().domainpart.parse().unwrap());
if let Some(query) = iq.query {
match query {
stanza::client::iq::Query::DiscoInfo(query) => {
@@ -594,7 +594,7 @@ pub async fn recv_iq<Fs: FileStore + Clone>(
}
Err(_e) => {
let iq = Iq {
- from: Some(connection.jid().clone()),
+ from: Some(connection.jid().clone().into()),
id: iq.id,
to: iq.from,
r#type: IqType::Error,
@@ -614,7 +614,7 @@ pub async fn recv_iq<Fs: FileStore + Clone>(
},
Err(_e) => {
let iq = Iq {
- from: Some(connection.jid().clone()),
+ from: Some(connection.jid().clone().into()),
id: iq.id,
to: iq.from,
r#type: IqType::Error,
@@ -634,7 +634,7 @@ pub async fn recv_iq<Fs: FileStore + Clone>(
}
};
let iq = Iq {
- from: Some(connection.jid().clone()),
+ from: Some(connection.jid().clone().into()),
id: iq.id,
to: iq.from,
r#type: IqType::Result,
@@ -653,7 +653,7 @@ pub async fn recv_iq<Fs: FileStore + Clone>(
_ => {
warn!("received unsupported iq get from {}", from);
let iq = Iq {
- from: Some(connection.jid().clone()),
+ from: Some(connection.jid().clone().into()),
id: iq.id,
to: iq.from,
r#type: IqType::Error,
@@ -677,7 +677,7 @@ pub async fn recv_iq<Fs: FileStore + Clone>(
} else {
info!("received malformed iq query from {}", from);
let iq = Iq {
- from: Some(connection.jid().clone()),
+ from: Some(connection.jid().clone().into()),
id: iq.id,
to: iq.from,
r#type: IqType::Error,
@@ -698,7 +698,8 @@ pub async fn recv_iq<Fs: FileStore + Clone>(
let from = iq
.from
.clone()
- .unwrap_or_else(|| connection.server().clone());
+ // .unwrap_or_else(|| connection.server().clone());
+ .unwrap_or_else(|| connection.jid().domainpart.parse().unwrap());
if let Some(query) = iq.query {
match query {
stanza::client::iq::Query::Roster(mut query) => {
@@ -725,7 +726,7 @@ pub async fn recv_iq<Fs: FileStore + Clone>(
.await;
}
let iq = Iq {
- from: Some(connection.jid().clone()),
+ from: Some(connection.jid().clone().into()),
id: iq.id,
to: iq.from,
r#type: IqType::Result,
@@ -751,7 +752,7 @@ pub async fn recv_iq<Fs: FileStore + Clone>(
} else {
warn!("received malformed roster push");
let iq = Iq {
- from: Some(connection.jid().clone()),
+ from: Some(connection.jid().clone().into()),
id: iq.id,
to: iq.from,
r#type: IqType::Error,
@@ -771,7 +772,7 @@ pub async fn recv_iq<Fs: FileStore + Clone>(
_ => {
warn!("received unsupported iq set from {}", from);
let iq = Iq {
- from: Some(connection.jid().clone()),
+ from: Some(connection.jid().clone().into()),
id: iq.id,
to: iq.from,
r#type: IqType::Error,
@@ -791,7 +792,7 @@ pub async fn recv_iq<Fs: FileStore + Clone>(
} else {
warn!("received malformed iq set from {}", from);
let iq = Iq {
- from: Some(connection.jid().clone()),
+ from: Some(connection.jid().clone().into()),
id: iq.id,
to: iq.from,
r#type: IqType::Error,
@@ -820,7 +821,7 @@ pub async fn process_stanza<Fs: FileStore + Clone>(
Stanza::Presence(presence) => Ok(recv_presence(presence).await?),
Stanza::Iq(iq) => Ok(recv_iq(logic, connection.clone(), iq).await?),
// unreachable, always caught by lampada
- // TODO: make cleaner than this in some way
+ // TODO: make cleaner than this in some way, by just reexporting a stanza enum from lampada ig.
Stanza::Error(error) => {
unreachable!()
}
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 6b61e10..70f5bca 100644
--- a/filamento/src/roster.rs
+++ b/filamento/src/roster.rs
@@ -1,6 +1,10 @@
+// SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
use std::{collections::HashSet, fmt::Display};
-use jid::JID;
+use jid::BareJID;
use rusqlite::{
ToSql,
types::{FromSql, ToSqlOutput, Value},
@@ -15,7 +19,7 @@ pub struct ContactUpdate {
#[cfg_attr(feature = "reactive_stores", derive(reactive_stores::Store))]
pub struct Contact {
// jid is the id used to reference everything, but not the primary key
- pub user_jid: JID,
+ pub user_jid: BareJID,
pub subscription: Subscription,
/// client user defined name
pub name: Option<String>,
diff --git a/filamento/src/user.rs b/filamento/src/user.rs
index f30933c..dc632ea 100644
--- a/filamento/src/user.rs
+++ b/filamento/src/user.rs
@@ -1,10 +1,14 @@
-use jid::JID;
+// SPDX-FileCopyrightText: 2025 cel <cel@bunny.garden>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+use jid::BareJID;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "reactive_stores", derive(reactive_stores::Store))]
pub struct User {
- pub jid: JID,
+ pub jid: BareJID,
pub nick: Option<String>,
pub avatar: Option<String>,
// pub cached_status_message: Option<String>,