Merge MSN sessions on nick changes

Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
This commit is contained in:
Maxime “pep” Buquet 2023-01-02 21:08:51 +01:00
parent 3f90060c08
commit 18050c1185
Signed by: pep
GPG key ID: DEDA74AEECA9D0F2
2 changed files with 174 additions and 26 deletions

View file

@ -347,13 +347,18 @@ impl Room {
let newnick = &new_session.participant().resource; let newnick = &new_session.participant().resource;
let bare = BareJid::from(new_session.real().clone()); let bare = BareJid::from(new_session.real().clone());
let mut merge = false;
for (nick, occupant) in self.occupants.iter_mut() { for (nick, occupant) in self.occupants.iter_mut() {
if nick == newnick && occupant.real != bare { if nick == newnick {
return Err(Error::NickAlreadyAssigned(newnick.clone())); if occupant.real != bare {
return Err(Error::NickAlreadyAssigned(newnick.clone()));
} else {
merge = true;
break;
}
} }
} }
// Send unavailable for current session to everyone // Send unavailable for current session to everyone
let presence_leave = Presence::new(PresenceType::Unavailable) let presence_leave = Presence::new(PresenceType::Unavailable)
@ -402,13 +407,29 @@ impl Room {
} }
} else { } else {
// Other occupants' sessions // Other occupants' sessions
component if occupant.real == bare {
.send_stanza( // Same barejid
presence_leave_to_others let mucuser = MucUser {
.clone() status: vec![MucStatus::NewNick],
.with_to(Jid::Full(session.real().clone())), items: vec![MucItem::new(Affiliation::Owner, Role::None)
) .with_nick(newnick.clone())
.await?; .with_jid(joined_session.real().clone())],
};
let presence = presence_leave
.clone()
.with_to(Jid::Full(session.real().clone()))
.with_payloads(vec![mucuser.into()]);
component.send_stanza(presence).await?;
} else {
component
.send_stanza(
presence_leave_to_others
.clone()
.with_to(Jid::Full(session.real().clone())),
)
.await?;
}
} }
} }
} }
@ -424,9 +445,17 @@ impl Room {
_ => unreachable!(), _ => unreachable!(),
} }
// Nickname isn't present already in the room. Create new occupant. if merge {
let occupant = Occupant::new(new_session.presence.clone())?; // Merge the new session
self.occupants.insert(newnick.to_string(), occupant.clone()); match self.get_mut_occupant(&new_session) {
Ok(occupant) => occupant.add_session(new_session.presence.clone())?,
_ => unreachable!(), // We've already established that the occupant existed.
}
} else {
// Nickname isn't present already in the room. Create new occupant.
let occupant = Occupant::new(new_session.presence.clone())?;
self.occupants.insert(newnick.to_string(), occupant.clone());
}
// Send available for new session to everyone // Send available for new session to everyone
@ -439,23 +468,29 @@ impl Room {
} }
.into()]); .into()]);
let occupant = self.get_occupant(&new_session)?;
let mucuser = MucUser {
status: vec![],
items: {
occupant
.iter()
.map(|session| {
MucItem::new(Affiliation::Owner, Role::Moderator)
.with_jid(session.real().clone())
})
.collect::<Vec<_>>()
},
};
for (nick, occupant) in self.occupants.iter() { for (nick, occupant) in self.occupants.iter() {
for session in occupant.iter() { for session in occupant.iter() {
// Self occupant // Self occupant
if nick == newnick { if nick == newnick {
let mucuser = MucUser {
status: vec![],
items: vec![MucItem::new(Affiliation::Owner, Role::Moderator)
.with_jid(new_session.real().clone())],
};
if session.real() == new_session.real() { if session.real() == new_session.real() {
// Self session // Self session
let mucuser = MucUser { let mut mucuser = mucuser.clone();
status: vec![MucStatus::SelfPresence], mucuser.status = vec![MucStatus::SelfPresence];
items: vec![MucItem::new(Affiliation::Owner, Role::Moderator)
.with_jid(new_session.real().clone())],
};
component component
.send_stanza( .send_stanza(
presence_join presence_join

View file

@ -17,8 +17,8 @@ use crate::component::TestComponent;
use crate::handlers::handle_stanza; use crate::handlers::handle_stanza;
use crate::room::Room; use crate::room::Room;
use crate::tests::templates::{ use crate::tests::templates::{
new_room, LOUISE_FULL1, LOUISE_FULL2, LOUISE_NICK, LOUISE_ROOM1_PART, ROOM1_BARE, SUGAKO_FULL1, new_room, LOUISE_FULL1, LOUISE_FULL2, LOUISE_NICK, LOUISE_NICK2, LOUISE_ROOM1_PART,
SUGAKO_NICK, LOUISE_ROOM1_PART2, ROOM1_BARE, SUGAKO_FULL1, SUGAKO_NICK,
}; };
use std::collections::HashMap; use std::collections::HashMap;
@ -195,3 +195,116 @@ async fn leave() {
assert!(louise.is_some()); assert!(louise.is_some());
assert_eq!(louise.unwrap().sessions.len(), 1); assert_eq!(louise.unwrap().sessions.len(), 1);
} }
#[tokio::test]
async fn nickname_change_merge() {
let mut rooms: HashMap<BareJid, Room> = HashMap::new();
rooms.insert(
ROOM1_BARE.clone(),
new_room(
ROOM1_BARE.clone(),
vec![
(LOUISE_NICK, vec![LOUISE_FULL1.clone()]),
(LOUISE_NICK2, vec![LOUISE_FULL2.clone()]),
(SUGAKO_NICK, vec![SUGAKO_FULL1.clone()]),
],
)
.await,
);
// LOUISE_NICK2 merging with LOUISE_NICK
let update: Element = Presence::new(PresenceType::None)
.with_from(Jid::Full(LOUISE_FULL2.clone()))
.with_to(Jid::Full(LOUISE_ROOM1_PART.clone()))
.into();
let mut component = TestComponent::new(vec![update]);
// Unavailable to self
component.expect(
Presence::new(PresenceType::Unavailable)
.with_from(Jid::Full(LOUISE_ROOM1_PART2.clone()))
.with_to(Jid::Full(LOUISE_FULL2.clone()))
.with_payloads(vec![MucUser {
status: vec![MucStatus::SelfPresence, MucStatus::NewNick],
items: vec![MucItem::new(Affiliation::Owner, Role::None)
.with_nick(LOUISE_NICK)
.with_jid(LOUISE_FULL2.clone())],
}
.into()]),
);
// Available to self
component.expect(
Presence::new(PresenceType::None)
.with_from(Jid::Full(LOUISE_ROOM1_PART.clone()))
.with_to(Jid::Full(LOUISE_FULL2.clone()))
.with_payloads(vec![MucUser {
status: vec![MucStatus::SelfPresence],
items: vec![
MucItem::new(Affiliation::Owner, Role::Moderator)
.with_jid(LOUISE_FULL1.clone()),
MucItem::new(Affiliation::Owner, Role::Moderator)
.with_jid(LOUISE_FULL2.clone()),
],
}
.into()]),
);
// Unavailable to other MSN session
component.expect(
Presence::new(PresenceType::Unavailable)
.with_from(Jid::Full(LOUISE_ROOM1_PART2.clone()))
.with_to(Jid::Full(LOUISE_FULL1.clone()))
.with_payloads(vec![MucUser {
status: vec![MucStatus::NewNick],
items: vec![MucItem::new(Affiliation::Owner, Role::None)
.with_nick(LOUISE_NICK)
.with_jid(LOUISE_FULL2.clone())],
}
.into()]),
);
// Available to other MSN session
component.expect(
Presence::new(PresenceType::None)
.with_from(Jid::Full(LOUISE_ROOM1_PART.clone()))
.with_to(Jid::Full(LOUISE_FULL1.clone()))
.with_payloads(vec![MucUser {
status: vec![],
items: vec![
MucItem::new(Affiliation::Owner, Role::Moderator)
.with_jid(LOUISE_FULL1.clone()),
MucItem::new(Affiliation::Owner, Role::Moderator)
.with_jid(LOUISE_FULL2.clone()),
],
}
.into()]),
);
// Unavailable to Sugako
component.expect(
Presence::new(PresenceType::Unavailable)
.with_from(Jid::Full(LOUISE_ROOM1_PART2.clone()))
.with_to(Jid::Full(SUGAKO_FULL1.clone()))
.with_payloads(vec![MucUser {
status: vec![MucStatus::NewNick],
items: vec![MucItem::new(Affiliation::Owner, Role::None).with_nick(LOUISE_NICK)],
}
.into()]),
);
// Available to Sugako
component.expect(
Presence::new(PresenceType::None)
.with_from(Jid::Full(LOUISE_ROOM1_PART.clone()))
.with_to(Jid::Full(SUGAKO_FULL1.clone()))
.with_payloads(vec![MucUser {
status: vec![],
items: vec![MucItem::new(Affiliation::Owner, Role::Moderator)],
}
.into()]),
);
handle_stanza(&mut component, &mut rooms).await.unwrap();
}