Compare commits

...

16 Commits

Author SHA1 Message Date
Werner Kroneman e5dfc0cd62 Merge branch 'refactor-unittests' into 'main'
Draft: Refactoring Agent

See merge request xmpp-rs/xmpp-rs!299
2024-04-17 14:53:57 +00:00
Jonas Schäfer 1293e9a3eb jid: fix incorrect type on Jid::Full
This does not matter much because users need to replace usages of these
anyway, but it's better to have it right here to not cause additional
confusion.
2024-04-16 18:19:47 +02:00
Jonas Schäfer 054447d147 jid: add more testcases
Because why not!
2024-04-15 19:54:59 +02:00
Jonas Schäfer c08946aa7c jid: skip `at` and `slash` in comparison operators
They only contain cached information and thus don't need to be included
in comparison and identity operators.

Fixes #123.
2024-04-15 19:35:02 +02:00
Jonas Schäfer c895cb1009 jid: implement Borrow<Jid> on FullJid and BareJid
This allows to use them interchangably when looking up keys in hash sets
and dicts.
2024-04-15 18:21:24 +02:00
Jonas Schäfer 2e9c9411a3 jid: rewrite public types
This moves InnerJid into Jid and reformulates BareJid and FullJid in
terms of Jid.

Doing this has the key advantage that FullJid and BareJid can deref to
and borrow as Jid. This, in turn, has the advantage that they can be
used much more flexibly in HashMaps. However, this is (as we say in
Germany) future music; this commit only does the internal reworking.

Oh and also, it saves 20% memory on Jid objects.

Fixes #122 more thoroughly, or rather the original intent behind it.
2024-04-15 18:21:24 +02:00
Werner Kroneman e1677e7612 join_room also only depends on TokioXmppClient; room.rs now no longer references Agent at all. 2024-03-03 20:55:02 +01:00
Werner Kroneman ffff9ae618 Moved the optionality of the nickname to Agent instead of the lower-level method. 2024-03-03 20:35:27 +01:00
Werner Kroneman 857115d0de Changed leave_room to use TokioXmppClient instead of Agent. 2024-03-03 20:34:29 +01:00
Werner Kroneman e5d8d2d561 Added doc comment to send_message 2024-03-03 20:07:42 +01:00
Werner Kroneman ccdf800d12 Replaced the Agent dependency in send_message with TokioXmppClient. 2024-03-03 20:06:59 +01:00
Werner Kroneman b630ae44c5 Added doc comment to upload_file_with 2024-03-03 19:29:23 +01:00
Werner Kroneman fde92f2aee Refactored upload_file_with for readability. 2024-03-03 19:29:23 +01:00
Werner Kroneman 6f635bba17 Factored out slotslot_request_for_file function 2024-03-03 19:29:23 +01:00
Werner Kroneman 3e7f240122 Factored out create_muc_leave_room_status function. 2024-03-03 19:29:23 +01:00
Werner Kroneman 1868afeda3 Factored out create_muc_join_presence_stanza function. 2024-03-03 19:29:23 +01:00
25 changed files with 779 additions and 483 deletions

View File

@ -1,180 +0,0 @@
// Copyright (c) 2023 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#![deny(missing_docs)]
//! Provides a type for Jabber IDs.
//!
//! For usage, check the documentation on the `Jid` struct.
use crate::Error;
use core::num::NonZeroU16;
use memchr::memchr;
use std::borrow::Cow;
use std::str::FromStr;
use stringprep::{nameprep, nodeprep, resourceprep};
use crate::parts::{DomainRef, NodeRef, ResourceRef};
fn length_check(len: usize, error_empty: Error, error_too_long: Error) -> Result<(), Error> {
if len == 0 {
Err(error_empty)
} else if len > 1023 {
Err(error_too_long)
} else {
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub(crate) struct InnerJid {
pub(crate) normalized: String,
pub(crate) at: Option<NonZeroU16>,
pub(crate) slash: Option<NonZeroU16>,
}
impl InnerJid {
pub(crate) fn new(unnormalized: &str) -> Result<InnerJid, Error> {
let bytes = unnormalized.as_bytes();
let mut orig_at = memchr(b'@', bytes);
let mut orig_slash = memchr(b'/', bytes);
if orig_at.is_some() && orig_slash.is_some() && orig_at > orig_slash {
// This is part of the resource, not a node@domain separator.
orig_at = None;
}
let normalized = match (orig_at, orig_slash) {
(Some(at), Some(slash)) => {
let node = nodeprep(&unnormalized[..at]).map_err(|_| Error::NodePrep)?;
length_check(node.len(), Error::NodeEmpty, Error::NodeTooLong)?;
let domain = nameprep(&unnormalized[at + 1..slash]).map_err(|_| Error::NamePrep)?;
length_check(domain.len(), Error::DomainEmpty, Error::DomainTooLong)?;
let resource =
resourceprep(&unnormalized[slash + 1..]).map_err(|_| Error::ResourcePrep)?;
length_check(resource.len(), Error::ResourceEmpty, Error::ResourceTooLong)?;
orig_at = Some(node.len());
orig_slash = Some(node.len() + domain.len() + 1);
match (node, domain, resource) {
(Cow::Borrowed(_), Cow::Borrowed(_), Cow::Borrowed(_)) => {
unnormalized.to_string()
}
(node, domain, resource) => format!("{node}@{domain}/{resource}"),
}
}
(Some(at), None) => {
let node = nodeprep(&unnormalized[..at]).map_err(|_| Error::NodePrep)?;
length_check(node.len(), Error::NodeEmpty, Error::NodeTooLong)?;
let domain = nameprep(&unnormalized[at + 1..]).map_err(|_| Error::NamePrep)?;
length_check(domain.len(), Error::DomainEmpty, Error::DomainTooLong)?;
orig_at = Some(node.len());
match (node, domain) {
(Cow::Borrowed(_), Cow::Borrowed(_)) => unnormalized.to_string(),
(node, domain) => format!("{node}@{domain}"),
}
}
(None, Some(slash)) => {
let domain = nameprep(&unnormalized[..slash]).map_err(|_| Error::NamePrep)?;
length_check(domain.len(), Error::DomainEmpty, Error::DomainTooLong)?;
let resource =
resourceprep(&unnormalized[slash + 1..]).map_err(|_| Error::ResourcePrep)?;
length_check(resource.len(), Error::ResourceEmpty, Error::ResourceTooLong)?;
orig_slash = Some(domain.len());
match (domain, resource) {
(Cow::Borrowed(_), Cow::Borrowed(_)) => unnormalized.to_string(),
(domain, resource) => format!("{domain}/{resource}"),
}
}
(None, None) => {
let domain = nameprep(unnormalized).map_err(|_| Error::NamePrep)?;
length_check(domain.len(), Error::DomainEmpty, Error::DomainTooLong)?;
domain.into_owned()
}
};
Ok(InnerJid {
normalized,
at: orig_at.and_then(|x| NonZeroU16::new(x as u16)),
slash: orig_slash.and_then(|x| NonZeroU16::new(x as u16)),
})
}
pub(crate) fn node(&self) -> Option<&NodeRef> {
self.at.map(|at| {
let at = u16::from(at) as usize;
NodeRef::from_str_unchecked(&self.normalized[..at])
})
}
pub(crate) fn domain(&self) -> &DomainRef {
match (self.at, self.slash) {
(Some(at), Some(slash)) => {
let at = u16::from(at) as usize;
let slash = u16::from(slash) as usize;
DomainRef::from_str_unchecked(&self.normalized[at + 1..slash])
}
(Some(at), None) => {
let at = u16::from(at) as usize;
DomainRef::from_str_unchecked(&self.normalized[at + 1..])
}
(None, Some(slash)) => {
let slash = u16::from(slash) as usize;
DomainRef::from_str_unchecked(&self.normalized[..slash])
}
(None, None) => DomainRef::from_str_unchecked(&self.normalized),
}
}
pub(crate) fn resource(&self) -> Option<&ResourceRef> {
self.slash.map(|slash| {
let slash = u16::from(slash) as usize;
ResourceRef::from_str_unchecked(&self.normalized[slash + 1..])
})
}
#[inline(always)]
pub(crate) fn as_str(&self) -> &str {
self.normalized.as_str()
}
}
impl FromStr for InnerJid {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
InnerJid::new(s)
}
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! assert_size (
($t:ty, $sz:expr) => (
assert_eq!(::std::mem::size_of::<$t>(), $sz);
);
);
#[cfg(target_pointer_width = "32")]
#[test]
fn test_size() {
assert_size!(InnerJid, 16);
}
#[cfg(target_pointer_width = "64")]
#[test]
fn test_size() {
assert_size!(InnerJid, 32);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@ use std::str::FromStr;
use stringprep::{nameprep, nodeprep, resourceprep};
use crate::{BareJid, Error, InnerJid, Jid};
use crate::{BareJid, Error, Jid};
fn length_check(len: usize, error_empty: Error, error_too_long: Error) -> Result<(), Error> {
if len == 0 {
@ -250,18 +250,18 @@ impl DomainRef {
impl From<DomainPart> for BareJid {
fn from(other: DomainPart) -> Self {
BareJid {
inner: InnerJid {
normalized: other.0,
at: None,
slash: None,
},
inner: other.into(),
}
}
}
impl From<DomainPart> for Jid {
fn from(other: DomainPart) -> Self {
Jid::Bare(other.into())
Jid {
normalized: other.0,
at: None,
slash: None,
}
}
}

View File

@ -88,7 +88,7 @@ impl From<BindResponse> for FullJid {
impl From<BindResponse> for Jid {
fn from(bind: BindResponse) -> Jid {
Jid::Full(bind.jid)
Jid::from(bind.jid)
}
}

View File

@ -71,8 +71,8 @@ mod tests {
assert_size!(Enable, 0);
assert_size!(Disable, 0);
assert_size!(Private, 0);
assert_size!(Received, 152);
assert_size!(Sent, 152);
assert_size!(Received, 140);
assert_size!(Sent, 140);
}
#[cfg(target_pointer_width = "64")]
@ -81,8 +81,8 @@ mod tests {
assert_size!(Enable, 0);
assert_size!(Disable, 0);
assert_size!(Private, 0);
assert_size!(Received, 288);
assert_size!(Sent, 288);
assert_size!(Received, 264);
assert_size!(Sent, 264);
}
#[test]

View File

@ -40,13 +40,13 @@ mod tests {
#[cfg(target_pointer_width = "32")]
#[test]
fn test_size() {
assert_size!(Delay, 48);
assert_size!(Delay, 44);
}
#[cfg(target_pointer_width = "64")]
#[test]
fn test_size() {
assert_size!(Delay, 80);
assert_size!(Delay, 72);
}
#[test]

View File

@ -248,7 +248,7 @@ mod tests {
assert_size!(DiscoInfoQuery, 12);
assert_size!(DiscoInfoResult, 48);
assert_size!(Item, 44);
assert_size!(Item, 40);
assert_size!(DiscoItemsQuery, 52);
assert_size!(DiscoItemsResult, 64);
}
@ -261,7 +261,7 @@ mod tests {
assert_size!(DiscoInfoQuery, 24);
assert_size!(DiscoInfoResult, 96);
assert_size!(Item, 88);
assert_size!(Item, 80);
assert_size!(DiscoItemsQuery, 104);
assert_size!(DiscoItemsResult, 128);
}

View File

@ -32,13 +32,13 @@ mod tests {
#[cfg(target_pointer_width = "32")]
#[test]
fn test_size() {
assert_size!(Forwarded, 152);
assert_size!(Forwarded, 140);
}
#[cfg(target_pointer_width = "64")]
#[test]
fn test_size() {
assert_size!(Forwarded, 288);
assert_size!(Forwarded, 264);
}
#[test]

View File

@ -232,15 +232,15 @@ mod tests {
#[cfg(target_pointer_width = "32")]
#[test]
fn test_size() {
assert_size!(IqType, 96);
assert_size!(Iq, 148);
assert_size!(IqType, 92);
assert_size!(Iq, 156);
}
#[cfg(target_pointer_width = "64")]
#[test]
fn test_size() {
assert_size!(IqType, 192);
assert_size!(Iq, 296);
assert_size!(IqType, 184);
assert_size!(Iq, 272);
}
#[test]

View File

@ -46,14 +46,14 @@ mod tests {
#[test]
fn test_size() {
assert_size!(JidPrepQuery, 12);
assert_size!(JidPrepResponse, 20);
assert_size!(JidPrepResponse, 16);
}
#[cfg(target_pointer_width = "64")]
#[test]
fn test_size() {
assert_size!(JidPrepQuery, 24);
assert_size!(JidPrepResponse, 40);
assert_size!(JidPrepResponse, 32);
}
#[test]

View File

@ -692,7 +692,7 @@ mod tests {
assert_size!(Reason, 1);
assert_size!(ReasonElement, 16);
assert_size!(SessionId, 12);
assert_size!(Jingle, 112);
assert_size!(Jingle, 104);
}
#[cfg(target_pointer_width = "64")]
@ -711,7 +711,7 @@ mod tests {
assert_size!(Reason, 1);
assert_size!(ReasonElement, 32);
assert_size!(SessionId, 24);
assert_size!(Jingle, 224);
assert_size!(Jingle, 208);
}
#[test]

View File

@ -284,7 +284,7 @@ mod tests {
assert_size!(Mode, 1);
assert_size!(CandidateId, 12);
assert_size!(StreamId, 12);
assert_size!(Candidate, 60);
assert_size!(Candidate, 56);
assert_size!(TransportPayload, 16);
assert_size!(Transport, 44);
}
@ -296,7 +296,7 @@ mod tests {
assert_size!(Mode, 1);
assert_size!(CandidateId, 24);
assert_size!(StreamId, 24);
assert_size!(Candidate, 96);
assert_size!(Candidate, 88);
assert_size!(TransportPayload, 32);
assert_size!(Transport, 88);
}

View File

@ -166,7 +166,7 @@ mod tests {
fn test_size() {
assert_size!(QueryId, 12);
assert_size!(Query, 120);
assert_size!(Result_, 176);
assert_size!(Result_, 164);
assert_size!(Complete, 1);
assert_size!(Fin, 44);
}
@ -176,7 +176,7 @@ mod tests {
fn test_size() {
assert_size!(QueryId, 24);
assert_size!(Query, 240);
assert_size!(Result_, 336);
assert_size!(Result_, 312);
assert_size!(Complete, 1);
assert_size!(Fin, 88);
}

View File

@ -307,7 +307,7 @@ mod tests {
assert_size!(Body, 12);
assert_size!(Subject, 12);
assert_size!(Thread, 12);
assert_size!(Message, 104);
assert_size!(Message, 96);
}
#[cfg(target_pointer_width = "64")]
@ -317,7 +317,7 @@ mod tests {
assert_size!(Body, 24);
assert_size!(Subject, 24);
assert_size!(Thread, 24);
assert_size!(Message, 208);
assert_size!(Message, 192);
}
#[test]

View File

@ -379,7 +379,7 @@ mod tests {
fn test_size() {
assert_size!(Show, 1);
assert_size!(Type, 1);
assert_size!(Presence, 80);
assert_size!(Presence, 72);
}
#[cfg(target_pointer_width = "64")]
@ -387,7 +387,7 @@ mod tests {
fn test_size() {
assert_size!(Show, 1);
assert_size!(Type, 1);
assert_size!(Presence, 160);
assert_size!(Presence, 144);
}
#[test]

View File

@ -200,11 +200,11 @@ mod tests {
node: NodeName(String::from("foo")),
affiliations: vec![
Affiliation {
jid: Jid::Bare(BareJid::from_str("hamlet@denmark.lit").unwrap()),
jid: Jid::from(BareJid::from_str("hamlet@denmark.lit").unwrap()),
affiliation: AffiliationAttribute::Owner,
},
Affiliation {
jid: Jid::Bare(BareJid::from_str("polonius@denmark.lit").unwrap()),
jid: Jid::from(BareJid::from_str("polonius@denmark.lit").unwrap()),
affiliation: AffiliationAttribute::Outcast,
},
],
@ -335,22 +335,22 @@ mod tests {
node: NodeName(String::from("foo")),
subscriptions: vec![
SubscriptionElem {
jid: Jid::Bare(BareJid::from_str("hamlet@denmark.lit").unwrap()),
jid: Jid::from(BareJid::from_str("hamlet@denmark.lit").unwrap()),
subscription: Subscription::Subscribed,
subid: None,
},
SubscriptionElem {
jid: Jid::Bare(BareJid::from_str("polonius@denmark.lit").unwrap()),
jid: Jid::from(BareJid::from_str("polonius@denmark.lit").unwrap()),
subscription: Subscription::Unconfigured,
subid: None,
},
SubscriptionElem {
jid: Jid::Bare(BareJid::from_str("bernardo@denmark.lit").unwrap()),
jid: Jid::from(BareJid::from_str("bernardo@denmark.lit").unwrap()),
subscription: Subscription::Subscribed,
subid: Some(String::from("123-abc")),
},
SubscriptionElem {
jid: Jid::Bare(BareJid::from_str("bernardo@denmark.lit").unwrap()),
jid: Jid::from(BareJid::from_str("bernardo@denmark.lit").unwrap()),
subscription: Subscription::Subscribed,
subid: Some(String::from("004-yyy")),
},

View File

@ -319,7 +319,7 @@ mod tests {
fn test_size() {
assert_size!(ErrorType, 1);
assert_size!(DefinedCondition, 1);
assert_size!(StanzaError, 96);
assert_size!(StanzaError, 92);
}
#[cfg(target_pointer_width = "64")]
@ -327,7 +327,7 @@ mod tests {
fn test_size() {
assert_size!(ErrorType, 1);
assert_size!(DefinedCondition, 1);
assert_size!(StanzaError, 192);
assert_size!(StanzaError, 184);
}
#[test]

View File

@ -44,14 +44,14 @@ mod tests {
#[cfg(target_pointer_width = "32")]
#[test]
fn test_size() {
assert_size!(StanzaId, 32);
assert_size!(StanzaId, 24);
assert_size!(OriginId, 12);
}
#[cfg(target_pointer_width = "64")]
#[test]
fn test_size() {
assert_size!(StanzaId, 64);
assert_size!(StanzaId, 56);
assert_size!(OriginId, 24);
}

View File

@ -75,7 +75,7 @@ async fn main() -> Result<(), Option<()>> {
Event::RoomJoined(jid) => {
println!("Joined room {}.", jid);
client
.send_message(Jid::Bare(jid), MessageType::Groupchat, "en", "Hello world!")
.send_message(Jid::from(jid), MessageType::Groupchat, "en", "Hello world!")
.await;
}
Event::RoomLeft(jid) => {

View File

@ -36,7 +36,9 @@ impl<C: ServerConnector> Agent<C> {
lang: &str,
status: &str,
) {
muc::room::join_room(self, room, nick, password, lang, status).await
// Use the provided nickname, or the default if none.
let nick = nick.unwrap_or_else(|| self.default_nick.read().unwrap().to_string());
muc::room::join_room(&mut self.client, room, &nick, password, lang, status).await
}
/// Request to leave a chatroom.
@ -57,7 +59,7 @@ impl<C: ServerConnector> Agent<C> {
lang: impl Into<String>,
status: impl Into<String>,
) {
muc::room::leave_room(self, room_jid, nickname, lang, status).await
muc::room::leave_room(&mut self.client, room_jid, nickname, lang, status).await
}
pub async fn send_message(
@ -67,7 +69,7 @@ impl<C: ServerConnector> Agent<C> {
lang: &str,
text: &str,
) {
message::send::send_message(self, recipient, type_, lang, text).await
message::send::send_message(&mut self.client, recipient, type_, lang, text).await
}
pub async fn send_room_private_message(
@ -90,7 +92,7 @@ impl<C: ServerConnector> Agent<C> {
event_loop::wait_for_events(self).await
}
pub async fn upload_file_with(&mut self, service: &str, path: &Path) {
pub async fn upload_file_with(&mut self, service: Jid, path: &Path) {
upload::send::upload_file_with(self, service, path).await
}

View File

@ -25,8 +25,8 @@ pub async fn handle_message_chat<C: ServerConnector>(
for payload in &message.payloads {
if let Ok(_) = MucUser::try_from(payload.clone()) {
let event = match from.clone() {
Jid::Bare(bare) => {
let event = match from.clone().try_into_full() {
Err(bare) => {
// TODO: Can a service message be of type Chat/Normal and not Groupchat?
warn!("Received misformed MessageType::Chat in muc#user namespace from a bare JID.");
Event::ServiceMessage(
@ -36,7 +36,7 @@ pub async fn handle_message_chat<C: ServerConnector>(
time_info.clone(),
)
}
Jid::Full(full) => Event::RoomPrivateMessage(
Ok(full) => Event::RoomPrivateMessage(
message.id.clone(),
full.to_bare(),
full.resource().to_string(),

View File

@ -28,17 +28,15 @@ pub async fn handle_message_group_chat<C: ServerConnector>(
}
if let Some((_lang, body)) = message.get_best_body(langs) {
let event = match from.clone() {
Jid::Full(full) => Event::RoomMessage(
let event = match from.clone().try_into_full() {
Ok(full) => Event::RoomMessage(
message.id.clone(),
from.to_bare(),
full.resource().to_string(),
body.clone(),
time_info,
),
Jid::Bare(bare) => {
Event::ServiceMessage(message.id.clone(), bare, body.clone(), time_info)
}
Err(bare) => Event::ServiceMessage(message.id.clone(), bare, body.clone(), time_info),
};
events.push(event)
}

View File

@ -10,10 +10,17 @@ use tokio_xmpp::{
Jid,
};
use crate::Agent;
use crate::agent::TokioXmppClient;
/// Send a text message to a recipient.
///
/// # Arguments
/// - `client`: The XMPP client to use to send the message.
/// - `recipient`: The JID of the recipient.
/// - `type_`: The type of the message.
/// - `lang`: The language of the message.
pub async fn send_message<C: ServerConnector>(
agent: &mut Agent<C>,
client: &mut TokioXmppClient<C>,
recipient: Jid,
type_: MessageType,
lang: &str,
@ -24,5 +31,5 @@ pub async fn send_message<C: ServerConnector>(
message
.bodies
.insert(String::from(lang), Body(String::from(text)));
let _ = agent.client.send_stanza(message.into()).await;
let _ = client.send_stanza(message.into()).await;
}

View File

@ -13,27 +13,58 @@ use tokio_xmpp::{
BareJid,
};
use crate::{Agent, RoomNick};
use crate::agent::TokioXmppClient;
use crate::RoomNick;
pub async fn join_room<C: ServerConnector>(
agent: &mut Agent<C>,
client: &mut TokioXmppClient<C>,
room: BareJid,
nick: Option<String>,
nick: &String,
password: Option<String>,
lang: &str,
status: &str,
) {
let presence = create_muc_join_presence_stanza(room, password, lang, status, nick);
let _ = client.send_stanza(presence.into()).await;
}
/// Create a Presence stanza for joining a MUC room with a specific nickname.
///
/// # Arguments
/// - `room`: The JID of the room to join.
/// - `password`: The password to use to join the room, if any.
/// - `lang`: The language of the status message.
/// - `status`: The status message to send.
/// - `nick`: The nickname to use in the room.
pub fn create_muc_join_presence_stanza(
room: BareJid,
password: Option<String>,
lang: &str,
status: &str,
nick: &String,
) -> Presence {
// Combine the room JID with the nickname to create the JID of the room occupant.
let room_jid = room.with_resource_str(&nick).unwrap();
// Create a presence stanza.
let mut presence = Presence::new(PresenceType::None).with_to(room_jid);
// MUC presence stanza's must have a Muc payload.
let mut muc = Muc::new();
// If a password is provided, include it in the Muc payload.
if let Some(password) = password {
muc = muc.with_password(password);
}
let nick = nick.unwrap_or_else(|| agent.default_nick.read().unwrap().clone());
let room_jid = room.with_resource_str(&nick).unwrap();
let mut presence = Presence::new(PresenceType::None).with_to(room_jid);
// Add the Muc payload to the presence stanza.
presence.add_payload(muc);
// Set the user-readable status message.
presence.set_status(String::from(lang), String::from(status));
let _ = agent.client.send_stanza(presence.into()).await;
// Return the presence stanza.
presence
}
/// Send a "leave room" request to the server (specifically, an "unavailable" presence stanza).
@ -51,17 +82,40 @@ pub async fn join_room<C: ServerConnector>(
///
/// # Arguments
///
/// * `client`: The XMPP client to use to send the status message.
/// * `room_jid`: The JID of the room to leave.
/// * `nickname`: The nickname to use in the room.
/// * `lang`: The language of the status message.
/// * `status`: The status message to send.
pub async fn leave_room<C: ServerConnector>(
agent: &mut Agent<C>,
client: &mut TokioXmppClient<C>,
room_jid: BareJid,
nickname: RoomNick,
lang: impl Into<String>,
status: impl Into<String>,
) {
let presence = create_muc_leave_room_status(room_jid, nickname, lang, status);
// Send the presence stanza.
if let Err(e) = client.send_stanza(presence.into()).await {
// Report any errors to the log.
error!("Failed to send leave room presence: {}", e);
}
}
/// Create a Presence stanza for leaving a MUC room that we're in with a specific nickname.
///
/// # Arguments
/// - `room_jid`: The JID of the room to leave.
/// - `nickname`: The nickname we are present in the room with.
/// - `lang`: The language of the status message.
/// - `status`: The status message to send.
pub fn create_muc_leave_room_status(
room_jid: BareJid,
nickname: RoomNick,
lang: impl Into<String> + Sized,
status: impl Into<String> + Sized,
) -> Presence {
// XEP-0045 specifies that, to leave a room, the client must send a presence stanza
// with type="unavailable".
let mut presence = Presence::new(PresenceType::Unavailable).with_to(
@ -74,10 +128,5 @@ pub async fn leave_room<C: ServerConnector>(
// TODO: Should this be optional? The XEP says "MAY", but the method signature requires the arguments.
// XEP-0045: "The occupant MAY include normal <status/> information in the unavailable presence stanzas"
presence.set_status(lang, status);
// Send the presence stanza.
if let Err(e) = agent.client.send_stanza(presence.into()).await {
// Report any errors to the log.
error!("Failed to send leave room presence: {}", e);
}
presence
}

View File

@ -14,23 +14,44 @@ use tokio_xmpp::{
use crate::Agent;
pub async fn upload_file_with<C: ServerConnector>(
agent: &mut Agent<C>,
service: &str,
path: &Path,
) {
/// Upload a file to the HTTP server associated with a given Jid.
///
/// # Arguments
/// - `agent`: The XMPP agent through which to negociate the request.
/// - `service`: The Jid of the HTTP server to upload the file to.
/// - `path`: The path to the file to upload.
pub async fn upload_file_with<C: ServerConnector>(agent: &mut Agent<C>, service: Jid, path: &Path) {
// Create the IQ request to upload the file.
let request =
Iq::from_get("upload1", slotslot_request_for_file(path).await).with_to(service.clone());
// Record the upload request so we can handle the response later.
agent
.uploads
.push((String::from("upload1"), service, path.to_path_buf()));
// Send the request to the server.
agent.client.send_stanza(request.into()).await.unwrap();
}
/// Create a SlotRequest for a file, representing a request for a URL to upload the file to.
///
/// Note: this function is async because it accesses the file system to read the file's metadata.
///
/// # Arguments
/// - `path`: The path to the file to upload.
async fn slotslot_request_for_file(path: &Path) -> SlotRequest {
// Extract the file's name.
let name = path.file_name().unwrap().to_str().unwrap().to_string();
// Open the file and read its size.
let file = File::open(path).await.unwrap();
let size = file.metadata().await.unwrap().len();
let slot_request = SlotRequest {
// Create a SlotRequest for the file.
SlotRequest {
filename: name,
size: size,
content_type: None,
};
let to = service.parse::<Jid>().unwrap();
let request = Iq::from_get("upload1", slot_request).with_to(to.clone());
agent
.uploads
.push((String::from("upload1"), to, path.to_path_buf()));
agent.client.send_stanza(request.into()).await.unwrap();
}
}