diff --git a/src/room.rs b/src/room.rs index 4c5fa47..8d3f07c 100644 --- a/src/room.rs +++ b/src/room.rs @@ -39,6 +39,9 @@ pub type Nick = String; pub struct Room { pub jid: BareJid, pub occupants: HashMap, + // TODO: Subject struct. + // TODO: Store subject lang + pub subject: Option<(String, Occupant, DateTime)>, } impl Room { @@ -46,6 +49,7 @@ impl Room { Room { jid, occupants: HashMap::new(), + subject: None, } } @@ -61,6 +65,8 @@ impl Room { } else { debug!("{} is joining {}", realjid, self.jid); + let new_occupant = Occupant::new(&self, realjid.clone(), nick.clone()); + // Ensure nick isn't already assigned let _ = self.occupants.iter().try_for_each(|(_, occupant)| { let nick = nick.clone(); @@ -72,18 +78,21 @@ impl Room { // Send occupants debug!("Sending occupants for {}", realjid); - let presence = Presence::new(PresenceType::None).with_to(realjid.clone()); + let presence = Presence::new(PresenceType::None) + // New occupant with a single session + .with_to(new_occupant.sessions[0].clone()) + .with_payloads(vec![MucUser { + status: Vec::new(), + items: vec![MucItem::new(Affiliation::Owner, Role::Moderator)], + }.into()]); for (_, occupant) in self.occupants.iter() { - for session in occupant.iter() { - let presence = presence.clone().with_from(session.clone()); - component.send_stanza(presence).await?; - } + let presence = presence.clone() + .with_from(occupant.participant.clone()); + component.send_stanza(presence).await?; } // Add into occupants - let _ = self - .occupants - .insert(bare.clone(), Occupant::new(realjid.clone(), nick.clone())); + let _ = self.occupants.insert(bare.clone(), new_occupant.clone()); // Self-presence debug!("Sending self-presence for {}", realjid); @@ -98,15 +107,25 @@ impl Room { // Send subject debug!("Sending subject!"); + if self.subject.is_none() { + let subject = String::from(""); + let setter = new_occupant; + let stamp = DateTime::from_utc(chrono::Utc::now()); + self.subject = Some((subject, setter, stamp)); + } + let mut subject = Message::new(Some(Jid::Full(realjid))); - subject.from = Some(Jid::Full(participant)); - subject - .subjects - .insert(String::from("en"), Subject(String::from(""))); + subject.from = Some(Jid::Full( + self.subject.as_ref().unwrap().1.participant.clone(), + )); + subject.subjects.insert( + String::from("en"), + Subject(self.subject.as_ref().unwrap().0.clone()), + ); subject.type_ = MessageType::Groupchat; subject.payloads = vec![Delay { from: Some(Jid::Bare(self.jid.clone())), - stamp: DateTime::from_utc(chrono::Utc::now()), + stamp: self.subject.as_ref().unwrap().2.clone(), data: None, } .into()]; @@ -119,23 +138,26 @@ impl Room { #[derive(Debug, Clone)] pub struct Occupant { - jid: BareJid, + /// Public Jid for the Occupant + real: BareJid, + participant: FullJid, nick: Nick, sessions: Vec, } impl Occupant { - fn new(fulljid: FullJid, nick: Nick) -> Occupant { + fn new(room: &Room, real: FullJid, nick: Nick) -> Occupant { Occupant { - jid: BareJid::from(fulljid.clone()), + real: BareJid::from(real.clone()), + participant: room.jid.clone().with_resource(nick.clone()), nick, - sessions: vec![fulljid], + sessions: vec![real], } } - pub fn add_session(&mut self, fulljid: FullJid) -> Result<(), Error> { - if BareJid::from(fulljid.clone()) != self.jid { - return Err(Error::MismatchJids(Jid::from(fulljid.clone()))); + pub fn add_session(&mut self, real: FullJid) -> Result<(), Error> { + if BareJid::from(real.clone()) != self.real { + return Err(Error::MismatchJids(Jid::from(real.clone()))); } Ok(()) @@ -161,13 +183,14 @@ impl Occupant { mod tests { use super::*; use std::str::FromStr; - use xmpp_parsers::FullJid; + use xmpp_parsers::{BareJid, FullJid}; #[test] fn occupant_ignore_dup_session() { - let fulljid = FullJid::from_str("foo@bar/meh").unwrap(); - let mut occupant = Occupant::new(fulljid.clone(), String::from("nick")); - occupant.add_session(fulljid.clone()).unwrap(); + let room = Room::new(BareJid::from_str("room@muc").unwrap()); + let real = FullJid::from_str("foo@bar/meh").unwrap(); + let mut occupant = Occupant::new(&room, real.clone(), String::from("nick")); + occupant.add_session(real.clone()).unwrap(); assert_eq!(occupant.iter().count(), 1); } } diff --git a/src/tests.rs b/src/tests.rs index 0cad031..f861140 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -201,3 +201,90 @@ async fn test_join_presence_nick_already_assigned() { None => panic!(), } } + +#[tokio::test] +async fn test_join_presence_existing_room() { + 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 mut component = TestComponent::new(vec![join1, join2]); + let mut rooms: HashMap = HashMap::new(); + + // Ignore self-presence for first participant + component.expect_with(|_| ()); + // Ignore message subject for first participant + component.expect_with(|_| ()); + + // Occupant presences for participant2 + component.expect( + Presence::new(PresenceType::None) + .with_from(Jid::Full(participant1.clone())) + .with_to(Jid::Full(realjid2.clone())) + .with_payloads(vec![MucUser { + status: Vec::new(), + items: vec![MucItem::new(Affiliation::Owner, Role::Moderator)], + }.into()]) + ); + + // self-presence for participant2 + component.expect( + Presence::new(PresenceType::None) + .with_from(Jid::Full(participant2)) + .with_to(Jid::Full(realjid2.clone())) + .with_payloads(vec![MucUser { + status: vec![MucStatus::SelfPresence, MucStatus::AssignedNick], + items: vec![MucItem::new(Affiliation::Owner, Role::Moderator)], + }.into()]) + ); + + // Subject for participant2 + component.expect_with(|el| { + let mut subjects = BTreeMap::new(); + subjects.insert(String::from("en"), MessageSubject::from_str("").unwrap()); + let expected: Element = Message { + // Set by the first participant + from: Some(Jid::Full(participant1)), + to: Some(Jid::Full(realjid2)), + id: None, + type_: MessageType::Groupchat, + bodies: BTreeMap::new(), + subjects, + thread: None, + payloads: Vec::new(), + }.into(); + + let mut out = Message::try_from(el).unwrap(); + // Check that is present. Don't check content as it's checked in a test earlier + assert_eq!(out.payloads.len(), 1); + let _delay = Delay::try_from(out.payloads[0].clone()).unwrap(); + + out.payloads = vec![]; + assert_eq!( + String::from(&expected), + String::from(&Into::::into(out)) + ); + }); + + handle_stanza(&mut component, &mut rooms).await.unwrap(); + component.assert(); + + match rooms.get(&roomjid) { + Some(room) => assert_eq!(room.occupants.len(), 2), + None => panic!(), + } +}