We can now leave rooms!
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
This commit is contained in:
parent
b7ee30c3f7
commit
d383aa8655
4 changed files with 268 additions and 41 deletions
|
@ -17,12 +17,13 @@ use std::error::Error as StdError;
|
|||
use std::fmt;
|
||||
|
||||
use tokio_xmpp::Error as TokioXMPPError;
|
||||
use xmpp_parsers::Jid;
|
||||
use xmpp_parsers::{FullJid, Jid};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
MismatchJids(Jid),
|
||||
NickAlreadyAssigned(String),
|
||||
NonexistantSession(FullJid),
|
||||
XMPPError(TokioXMPPError),
|
||||
}
|
||||
|
||||
|
@ -33,6 +34,7 @@ impl fmt::Display for Error {
|
|||
match self {
|
||||
Error::MismatchJids(err) => write!(f, "Mismatch Jids: {}", err),
|
||||
Error::NickAlreadyAssigned(err) => write!(f, "Nickname already assigned: {}", err),
|
||||
Error::NonexistantSession(err) => write!(f, "Session doesn't exist: {}", err),
|
||||
Error::XMPPError(err) => write!(f, "XMPP error: {}", err),
|
||||
}
|
||||
}
|
||||
|
|
116
src/handlers.rs
116
src/handlers.rs
|
@ -106,51 +106,87 @@ async fn handle_presence<C: ComponentTrait>(
|
|||
presence: Presence,
|
||||
rooms: &mut HashMap<BareJid, Room>,
|
||||
) -> Result<(), Error> {
|
||||
let muc = presence
|
||||
.payloads
|
||||
.into_iter()
|
||||
.try_for_each(|payload| match Muc::try_from(payload) {
|
||||
Ok(muc) => ControlFlow::Break(muc),
|
||||
_ => ControlFlow::Continue(()),
|
||||
});
|
||||
if presence.type_ == PresenceType::None {
|
||||
let muc = presence
|
||||
.payloads
|
||||
.into_iter()
|
||||
.try_for_each(|payload| match Muc::try_from(payload) {
|
||||
Ok(muc) => ControlFlow::Break(muc),
|
||||
_ => ControlFlow::Continue(()),
|
||||
});
|
||||
|
||||
if let ControlFlow::Continue(_) = muc {
|
||||
return Ok(());
|
||||
}
|
||||
// TODO: Handle presence probes, remove the return here.
|
||||
// TODO: check for features in the Muc element.
|
||||
if let ControlFlow::Continue(_) = muc {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Presences to MUC come from resources not accounts
|
||||
if let Jid::Full(realjid) = presence.from.unwrap() &&
|
||||
let Jid::Full(participant) = presence.to.unwrap() {
|
||||
// Presences to MUC come from resources not accounts
|
||||
if let Jid::Full(realjid) = presence.from.clone().unwrap() &&
|
||||
let Jid::Full(participant) = presence.to.clone().unwrap() {
|
||||
|
||||
let roomjid = BareJid::from(participant.clone());
|
||||
let nick: Nick = participant.resource.clone();
|
||||
let roomjid = BareJid::from(participant.clone());
|
||||
let nick: Nick = participant.resource.clone();
|
||||
|
||||
// Room already exists
|
||||
if let Some(room) = rooms.get_mut(&roomjid) {
|
||||
debug!("Presence received to existing room: {}", &roomjid);
|
||||
match room.add_session(component, realjid.clone(), nick).await {
|
||||
Ok(_) => (),
|
||||
Err(Error::NickAlreadyAssigned(nick)) => {
|
||||
let error = Presence::new(PresenceType::Error)
|
||||
.with_from(participant)
|
||||
.with_to(realjid)
|
||||
.with_payloads(vec![
|
||||
StanzaError::new(
|
||||
ErrorType::Cancel,
|
||||
DefinedCondition::Conflict,
|
||||
"en",
|
||||
format!("Nickname conflict: {}", nick),
|
||||
).into()
|
||||
]);
|
||||
component.send_stanza(error).await?;
|
||||
},
|
||||
err => err.unwrap(),
|
||||
// Room already exists
|
||||
if let Some(room) = rooms.get_mut(&roomjid) {
|
||||
debug!("Presence received to existing room: {}", &roomjid);
|
||||
match room.add_session(component, realjid.clone(), nick).await {
|
||||
Ok(_) => (),
|
||||
Err(Error::NickAlreadyAssigned(nick)) => {
|
||||
let error = Presence::new(PresenceType::Error)
|
||||
.with_from(participant)
|
||||
.with_to(realjid)
|
||||
.with_payloads(vec![
|
||||
StanzaError::new(
|
||||
ErrorType::Cancel,
|
||||
DefinedCondition::Conflict,
|
||||
"en",
|
||||
format!("Nickname conflict: {}", nick),
|
||||
).into()
|
||||
]);
|
||||
component.send_stanza(error).await?;
|
||||
},
|
||||
err => err.unwrap(),
|
||||
}
|
||||
} else {
|
||||
debug!("Presence received to new room: {}", &roomjid);
|
||||
let mut room = Room::new(roomjid.clone());
|
||||
room.add_session(component, realjid, nick).await.unwrap();
|
||||
let _ = rooms.insert(roomjid, room);
|
||||
}
|
||||
}
|
||||
} else if presence.type_ == PresenceType::Unavailable {
|
||||
if let Jid::Full(realjid) = presence.from.unwrap() &&
|
||||
let Jid::Full(participant) = presence.to.unwrap() {
|
||||
|
||||
let roomjid = BareJid::from(participant.clone());
|
||||
|
||||
let error = Presence::new(PresenceType::Error)
|
||||
.with_from(participant.clone())
|
||||
.with_to(realjid.clone())
|
||||
.with_payloads(vec![
|
||||
StanzaError::new(
|
||||
ErrorType::Cancel,
|
||||
DefinedCondition::ServiceUnavailable,
|
||||
"en",
|
||||
"Occupant does not exist",
|
||||
).into()
|
||||
]);
|
||||
if let Some(mut room) = rooms.remove(&roomjid) {
|
||||
match room.remove_session(component, realjid).await {
|
||||
Ok(()) => (),
|
||||
Err(Error::NonexistantSession(_)) => {
|
||||
component.send_stanza(error).await.unwrap();
|
||||
},
|
||||
Err(err) => Err(err).unwrap(),
|
||||
}
|
||||
|
||||
// Add the room back
|
||||
if room.occupants.len() > 0 {
|
||||
rooms.insert(roomjid, room);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debug!("Presence received to new room: {}", &roomjid);
|
||||
let mut room = Room::new(roomjid.clone());
|
||||
room.add_session(component, realjid, nick).await.unwrap();
|
||||
let _ = rooms.insert(roomjid, room);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
61
src/room.rs
61
src/room.rs
|
@ -134,6 +134,51 @@ impl Room {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn remove_session<C: ComponentTrait>(
|
||||
&mut self,
|
||||
component: &mut C,
|
||||
realjid: FullJid,
|
||||
) -> Result<(), Error> {
|
||||
let bare = BareJid::from(realjid.clone());
|
||||
|
||||
// If occupant doesn't exist, ignore.
|
||||
if let Some(mut occupant) = self.occupants.remove(&bare) {
|
||||
let self_presence = Presence::new(PresenceType::Unavailable)
|
||||
.with_from(occupant.participant.clone())
|
||||
.with_to(realjid.clone())
|
||||
.with_payloads(vec![MucUser {
|
||||
status: vec![MucStatus::SelfPresence],
|
||||
items: vec![MucItem::new(Affiliation::Owner, Role::None)],
|
||||
}
|
||||
.into()]);
|
||||
|
||||
component.send_stanza(self_presence).await?;
|
||||
occupant.remove_session(realjid)?;
|
||||
|
||||
let presence = Presence::new(PresenceType::Unavailable)
|
||||
.with_from(occupant.participant.clone())
|
||||
.with_payloads(vec![MucUser {
|
||||
status: Vec::new(),
|
||||
items: vec![MucItem::new(Affiliation::Owner, Role::None)],
|
||||
}
|
||||
.into()]);
|
||||
|
||||
// Add remaining occupant sessions in the occupant pool
|
||||
if occupant.sessions.len() > 0 {
|
||||
self.occupants.insert(bare, occupant);
|
||||
}
|
||||
|
||||
for (_, occupant) in self.occupants.iter() {
|
||||
for session in occupant.iter() {
|
||||
let presence = presence.clone().with_to(Jid::Full(session.clone()));
|
||||
component.send_stanza(presence).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -162,6 +207,22 @@ impl Occupant {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove_session(&mut self, real: FullJid) -> Result<(), Error> {
|
||||
if BareJid::from(real.clone()) != self.real {
|
||||
return Err(Error::MismatchJids(Jid::from(real.clone())));
|
||||
}
|
||||
|
||||
let len = self.sessions.len();
|
||||
self.sessions.retain(|session| session != &real);
|
||||
|
||||
// An item has been removed
|
||||
if len != self.sessions.len() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::NonexistantSession(real))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for Occupant {
|
||||
|
|
128
src/tests.rs
128
src/tests.rs
|
@ -288,3 +288,131 @@ async fn test_join_presence_existing_room() {
|
|||
None => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_leave_non_existing_room() {
|
||||
let realjid1 = FullJid::from_str("foo@bar/qxx").unwrap();
|
||||
let roomjid = COMPONENT_JID.clone().with_node("room");
|
||||
let participant1 = roomjid.clone().with_resource("nick1");
|
||||
|
||||
let leave1: Element = Presence::new(PresenceType::Unavailable)
|
||||
.with_from(Jid::Full(realjid1.clone()))
|
||||
.with_to(Jid::Full(participant1.clone()))
|
||||
.into();
|
||||
|
||||
// Non-existing room
|
||||
let mut component = TestComponent::new(vec![leave1]);
|
||||
let mut rooms: HashMap<BareJid, Room> = HashMap::new();
|
||||
handle_stanza(&mut component, &mut rooms).await.unwrap();
|
||||
// The leave should be ignored, there should be no output at all.
|
||||
component.assert();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_leave_last_participant() {
|
||||
let realjid1 = FullJid::from_str("foo@bar/qxx").unwrap();
|
||||
let roomjid = COMPONENT_JID.clone().with_node("room");
|
||||
let participant1 = roomjid.clone().with_resource("nick1");
|
||||
|
||||
let join1: Element = Presence::new(PresenceType::None)
|
||||
.with_from(Jid::Full(realjid1.clone()))
|
||||
.with_to(Jid::Full(participant1.clone()))
|
||||
.with_payloads(vec![Muc::new().into()])
|
||||
.into();
|
||||
|
||||
let leave1: Element = Presence::new(PresenceType::Unavailable)
|
||||
.with_from(Jid::Full(realjid1.clone()))
|
||||
.with_to(Jid::Full(participant1.clone()))
|
||||
.into();
|
||||
|
||||
// Last occupant
|
||||
let mut component = TestComponent::new(vec![join1, leave1]);
|
||||
let mut rooms: HashMap<BareJid, Room> = HashMap::new();
|
||||
|
||||
// Ignore self-presence for participant1
|
||||
component.expect_with(|_| ());
|
||||
// Ignore subject message for participant1
|
||||
component.expect_with(|_| ());
|
||||
|
||||
component.expect(Presence::new(PresenceType::Unavailable)
|
||||
.with_from(Jid::Full(participant1.clone()))
|
||||
.with_to(Jid::Full(realjid1.clone()))
|
||||
.with_payloads(vec![MucUser {
|
||||
status: vec![MucStatus::SelfPresence],
|
||||
items: vec![MucItem::new(Affiliation::Owner, Role::None)],
|
||||
}.into()])
|
||||
);
|
||||
|
||||
handle_stanza(&mut component, &mut rooms).await.unwrap();
|
||||
|
||||
component.assert();
|
||||
assert_eq!(rooms.len(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_leave_room_not_last() {
|
||||
let realjid1 = FullJid::from_str("foo@bar/qxx").unwrap();
|
||||
let realjid2 = FullJid::from_str("qxx@bar/foo").unwrap();
|
||||
let roomjid = COMPONENT_JID.clone().with_node("room");
|
||||
let participant1 = roomjid.clone().with_resource("nick1");
|
||||
let participant2 = roomjid.clone().with_resource("nick2");
|
||||
|
||||
let join1: Element = Presence::new(PresenceType::None)
|
||||
.with_from(Jid::Full(realjid1.clone()))
|
||||
.with_to(Jid::Full(participant1.clone()))
|
||||
.with_payloads(vec![Muc::new().into()])
|
||||
.into();
|
||||
|
||||
let join2: Element = Presence::new(PresenceType::None)
|
||||
.with_from(Jid::Full(realjid2.clone()))
|
||||
.with_to(Jid::Full(participant2.clone()))
|
||||
.with_payloads(vec![Muc::new().into()])
|
||||
.into();
|
||||
|
||||
let leave2: Element = Presence::new(PresenceType::Unavailable)
|
||||
.with_from(Jid::Full(realjid2.clone()))
|
||||
.with_to(Jid::Full(participant2.clone()))
|
||||
.into();
|
||||
|
||||
|
||||
// Room still has occupants after leave
|
||||
let mut component = TestComponent::new(vec![join1, join2, leave2]);
|
||||
let mut rooms: HashMap<BareJid, Room> = HashMap::new();
|
||||
|
||||
// Ignore self-presence for participant1
|
||||
component.expect_with(|_| ());
|
||||
// Ignore subject message for participant1
|
||||
component.expect_with(|_| ());
|
||||
// Ignore participant1 occupant presence for participant2
|
||||
component.expect_with(|_| ());
|
||||
// Ignore self-presence for participant2
|
||||
component.expect_with(|_| ());
|
||||
// Ignore subject message for participant2
|
||||
component.expect_with(|_| ());
|
||||
|
||||
component.expect(Presence::new(PresenceType::Unavailable)
|
||||
.with_from(Jid::Full(participant2.clone()))
|
||||
.with_to(Jid::Full(realjid2.clone()))
|
||||
.with_payloads(vec![MucUser {
|
||||
status: vec![MucStatus::SelfPresence],
|
||||
items: vec![MucItem::new(Affiliation::Owner, Role::None)],
|
||||
}.into()])
|
||||
);
|
||||
|
||||
component.expect(Presence::new(PresenceType::Unavailable)
|
||||
.with_from(Jid::Full(participant2.clone()))
|
||||
.with_to(Jid::Full(realjid1.clone()))
|
||||
.with_payloads(vec![MucUser {
|
||||
status: Vec::new(),
|
||||
items: vec![MucItem::new(Affiliation::Owner, Role::None)],
|
||||
}.into()])
|
||||
);
|
||||
|
||||
handle_stanza(&mut component, &mut rooms).await.unwrap();
|
||||
component.assert();
|
||||
assert_eq!(rooms.len(), 1);
|
||||
match rooms.get(&roomjid) {
|
||||
Some(room) => assert_eq!(room.occupants.len(), 1),
|
||||
None => panic!(),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue