Split Occupant and Session into their own module
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
This commit is contained in:
parent
1463a55d3f
commit
2a2d8cd051
6 changed files with 290 additions and 232 deletions
|
@ -13,7 +13,7 @@
|
|||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::room::Session;
|
||||
use crate::session::Session;
|
||||
|
||||
use std::error::Error as StdError;
|
||||
use std::fmt;
|
||||
|
|
|
@ -21,7 +21,9 @@
|
|||
mod component;
|
||||
mod error;
|
||||
mod handlers;
|
||||
mod occupant;
|
||||
mod room;
|
||||
mod session;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
|
196
src/occupant.rs
Normal file
196
src/occupant.rs
Normal file
|
@ -0,0 +1,196 @@
|
|||
// Copyright (C) 2022-2099 The crate authors.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Affero General Public License as published by the
|
||||
// Free Software Foundation, either version 3 of the License, or (at your
|
||||
// option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
|
||||
// for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::session::{Nick, Session};
|
||||
|
||||
use std::iter::IntoIterator;
|
||||
|
||||
use xmpp_parsers::{presence::Presence, BareJid, FullJid, Jid};
|
||||
|
||||
/// An occupant in a room. May contain multiple sessions (Multi-Session Nicks)
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Occupant {
|
||||
/// Public Jid for the Occupant
|
||||
pub real: BareJid,
|
||||
pub participant: FullJid,
|
||||
pub nick: Nick,
|
||||
pub sessions: Vec<Session>,
|
||||
}
|
||||
|
||||
impl Occupant {
|
||||
/// New occupant
|
||||
pub fn new(presence: Presence) -> Result<Occupant, Error> {
|
||||
let session = Session::try_from(presence)?;
|
||||
Ok(Occupant {
|
||||
real: BareJid::from(session.real.clone()),
|
||||
participant: session.participant.clone(),
|
||||
nick: session.participant.resource.clone(),
|
||||
sessions: vec![session],
|
||||
})
|
||||
}
|
||||
|
||||
/// Add a new session to the occupant
|
||||
pub fn add_session(&mut self, presence: Presence) -> Result<(), Error> {
|
||||
let new_session = Session::try_from(presence)?;
|
||||
if BareJid::from(new_session.real.clone()) != self.real {
|
||||
return Err(Error::MismatchJids(
|
||||
Jid::from(self.real.clone()),
|
||||
Jid::from(new_session.real),
|
||||
));
|
||||
}
|
||||
|
||||
for session in &self.sessions {
|
||||
if &new_session.real == &session.real {
|
||||
return Err(Error::SessionAlreadyExists(new_session));
|
||||
}
|
||||
}
|
||||
|
||||
self.sessions.push(new_session);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove a session from the occupant
|
||||
pub fn remove_session(&mut self, presence: Presence) -> Result<(), Error> {
|
||||
let own_session = Session::try_from(presence)?;
|
||||
|
||||
let len = self.sessions.len();
|
||||
self.sessions
|
||||
.retain(|session| session.real != own_session.real);
|
||||
|
||||
// An item has been removed
|
||||
if len != self.sessions.len() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::NonexistantSession(own_session))
|
||||
}
|
||||
}
|
||||
|
||||
/// Update session presence
|
||||
pub fn update_presence(&mut self, presence: Presence) -> Result<(), Error> {
|
||||
let own_session = Session::with_nick(presence, self.participant.resource.clone())?;
|
||||
|
||||
for (i, session) in self.sessions.iter().enumerate() {
|
||||
if &own_session == session {
|
||||
if i == 0 {
|
||||
// Leader session
|
||||
self.sessions[0] = own_session;
|
||||
return Ok(());
|
||||
}
|
||||
return Err(Error::SecondarySession(own_session));
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::NonexistantSession(own_session))
|
||||
}
|
||||
|
||||
/// Return whether a Jid matches the occupant. If FullJid, compare with each session, otherwise
|
||||
/// see if the BareJid matches.
|
||||
// TODO: We may want to split checking for Bare/Full as this can be error prone
|
||||
pub fn contains<J: Into<Jid>>(&self, _jid: &J) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for Occupant {
|
||||
type Item = Session;
|
||||
type IntoIter = std::vec::IntoIter<Self::Item>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.sessions.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl Occupant {
|
||||
pub fn iter(&self) -> std::slice::Iter<'_, Session> {
|
||||
self.sessions.iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tests::templates::{
|
||||
LOUISE_FULL1, LOUISE_FULL2, LOUISE_NICK, LOUISE_ROOM1_PART, ROOM1_BARE,
|
||||
};
|
||||
use xmpp_parsers::{
|
||||
muc::{
|
||||
user::{Affiliation, Item as MucItem, Role},
|
||||
Muc, MucUser,
|
||||
},
|
||||
presence::{Presence, Show as PresenceShow, Type as PresenceType},
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_occupant_update_presence() {
|
||||
let presence_louise1 = Presence::new(PresenceType::None)
|
||||
.with_from(LOUISE_FULL1.clone())
|
||||
.with_to(LOUISE_ROOM1_PART.clone());
|
||||
let presence_louise2 = Presence::new(PresenceType::None)
|
||||
.with_from(LOUISE_FULL2.clone())
|
||||
.with_to(LOUISE_ROOM1_PART.clone());
|
||||
|
||||
let mut occupant = Occupant::new(presence_louise1).unwrap();
|
||||
occupant.add_session(presence_louise2).unwrap();
|
||||
|
||||
let presence1 = Presence::new(PresenceType::None)
|
||||
.with_from(LOUISE_FULL1.clone())
|
||||
.with_to(ROOM1_BARE.clone())
|
||||
.with_show(PresenceShow::Away)
|
||||
.with_payloads(vec![
|
||||
Muc::new().into(),
|
||||
MucUser {
|
||||
status: Vec::new(),
|
||||
items: vec![MucItem::new(Affiliation::Owner, Role::Moderator)],
|
||||
}
|
||||
.into(),
|
||||
]);
|
||||
match occupant.update_presence(presence1.clone()) {
|
||||
Ok(()) => (),
|
||||
err => panic!("Err: {:?}", err),
|
||||
}
|
||||
assert_eq!(occupant.sessions[0].presence, presence1);
|
||||
|
||||
let presence2 = Presence::new(PresenceType::None)
|
||||
.with_from(LOUISE_FULL2.clone())
|
||||
.with_to(ROOM1_BARE.clone())
|
||||
.with_show(PresenceShow::Xa);
|
||||
|
||||
match occupant.update_presence(presence2.clone()) {
|
||||
Err(Error::SecondarySession(session)) if session.real == *LOUISE_FULL2 => (),
|
||||
err => panic!(
|
||||
"Should return Error::SecondarySession(Session {{ {:?} }}), returned: {:?}",
|
||||
*LOUISE_FULL2, err,
|
||||
),
|
||||
}
|
||||
assert_eq!(
|
||||
occupant.sessions[1],
|
||||
Session::with_nick(presence2.clone(), LOUISE_NICK.clone()).unwrap()
|
||||
);
|
||||
|
||||
let presence_leave_louise2 = Presence::new(PresenceType::Unavailable)
|
||||
.with_from(LOUISE_FULL2.clone())
|
||||
.with_to(LOUISE_ROOM1_PART.clone());
|
||||
occupant.remove_session(presence_leave_louise2).unwrap();
|
||||
|
||||
match occupant.update_presence(presence2) {
|
||||
Err(Error::NonexistantSession(session)) if session.real == *LOUISE_FULL2 => (),
|
||||
err => panic!(
|
||||
"Should return Error::SecondarySession(Session {{ {:?} }}), returned: {:?}",
|
||||
*LOUISE_FULL2, err,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
238
src/room.rs
238
src/room.rs
|
@ -15,9 +15,10 @@
|
|||
|
||||
use crate::component::ComponentTrait;
|
||||
use crate::error::Error;
|
||||
use crate::occupant::Occupant;
|
||||
use crate::session::{Nick, Session};
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::iter::IntoIterator;
|
||||
|
||||
use chrono::{FixedOffset, Utc};
|
||||
use log::debug;
|
||||
|
@ -30,11 +31,9 @@ use xmpp_parsers::{
|
|||
MucUser,
|
||||
},
|
||||
presence::{Presence, Type as PresenceType},
|
||||
BareJid, FullJid, Jid,
|
||||
BareJid, Jid,
|
||||
};
|
||||
|
||||
pub type Nick = String;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum BroadcastPresence {
|
||||
/// Resource joined the room. It needs to know about all other participants, and other
|
||||
|
@ -383,182 +382,22 @@ impl Room {
|
|||
}
|
||||
}
|
||||
|
||||
/// An occupant session
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Session {
|
||||
pub presence: Presence,
|
||||
pub real: FullJid,
|
||||
pub participant: FullJid,
|
||||
}
|
||||
|
||||
impl Session {
|
||||
/// Ensure presence doesn't contain payloads that would impersonate us
|
||||
fn filter_presence(presence: Presence) -> Presence {
|
||||
presence
|
||||
}
|
||||
|
||||
/// Instanciate a Session from a presence which is adressed to the room JID.
|
||||
fn with_nick<N: Into<Nick>>(presence: Presence, nick: N) -> Result<Session, Error> {
|
||||
let presence = Session::filter_presence(presence);
|
||||
Ok(Session {
|
||||
real: presence
|
||||
.from
|
||||
.clone()
|
||||
.map(FullJid::try_from)
|
||||
.ok_or(Error::MissingJid)??,
|
||||
participant: presence
|
||||
.to
|
||||
.clone()
|
||||
.map(BareJid::from)
|
||||
.ok_or(Error::MissingJid)?
|
||||
.with_resource(nick.into()),
|
||||
presence,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Session {
|
||||
fn eq(&self, other: &Session) -> bool {
|
||||
self.real == other.real && self.participant == other.participant
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Presence> for Session {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(presence: Presence) -> Result<Session, Error> {
|
||||
let presence = Session::filter_presence(presence);
|
||||
Ok(Session {
|
||||
real: presence
|
||||
.from
|
||||
.clone()
|
||||
.map(FullJid::try_from)
|
||||
.ok_or(Error::MissingJid)??,
|
||||
participant: presence
|
||||
.to
|
||||
.clone()
|
||||
.map(FullJid::try_from)
|
||||
.ok_or(Error::MissingJid)??,
|
||||
presence,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// An occupant in a room. May contain multiple sessions (Multi-Session Nicks)
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Occupant {
|
||||
/// Public Jid for the Occupant
|
||||
pub real: BareJid,
|
||||
pub participant: FullJid,
|
||||
pub nick: Nick,
|
||||
pub sessions: Vec<Session>,
|
||||
}
|
||||
|
||||
impl Occupant {
|
||||
/// New occupant
|
||||
pub fn new(presence: Presence) -> Result<Occupant, Error> {
|
||||
let session = Session::try_from(presence)?;
|
||||
Ok(Occupant {
|
||||
real: BareJid::from(session.real.clone()),
|
||||
participant: session.participant.clone(),
|
||||
nick: session.participant.resource.clone(),
|
||||
sessions: vec![session],
|
||||
})
|
||||
}
|
||||
|
||||
/// Add a new session to the occupant
|
||||
pub fn add_session(&mut self, presence: Presence) -> Result<(), Error> {
|
||||
let new_session = Session::try_from(presence)?;
|
||||
if BareJid::from(new_session.real.clone()) != self.real {
|
||||
return Err(Error::MismatchJids(
|
||||
Jid::from(self.real.clone()),
|
||||
Jid::from(new_session.real),
|
||||
));
|
||||
}
|
||||
|
||||
for session in &self.sessions {
|
||||
if &new_session.real == &session.real {
|
||||
return Err(Error::SessionAlreadyExists(new_session));
|
||||
}
|
||||
}
|
||||
|
||||
self.sessions.push(new_session);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove a session from the occupant
|
||||
pub fn remove_session(&mut self, presence: Presence) -> Result<(), Error> {
|
||||
let own_session = Session::try_from(presence)?;
|
||||
|
||||
let len = self.sessions.len();
|
||||
self.sessions
|
||||
.retain(|session| session.real != own_session.real);
|
||||
|
||||
// An item has been removed
|
||||
if len != self.sessions.len() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::NonexistantSession(own_session))
|
||||
}
|
||||
}
|
||||
|
||||
/// Update session presence
|
||||
pub fn update_presence(&mut self, presence: Presence) -> Result<(), Error> {
|
||||
let own_session = Session::with_nick(presence, self.participant.resource.clone())?;
|
||||
|
||||
for (i, session) in self.sessions.iter().enumerate() {
|
||||
if &own_session == session {
|
||||
if i == 0 {
|
||||
// Leader session
|
||||
self.sessions[0] = own_session;
|
||||
return Ok(());
|
||||
}
|
||||
return Err(Error::SecondarySession(own_session));
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::NonexistantSession(own_session))
|
||||
}
|
||||
|
||||
/// Return whether a Jid matches the occupant. If FullJid, compare with each session, otherwise
|
||||
/// see if the BareJid matches.
|
||||
// TODO: We may want to split checking for Bare/Full as this can be error prone
|
||||
pub fn contains<J: Into<Jid>>(&self, _jid: &J) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for Occupant {
|
||||
type Item = Session;
|
||||
type IntoIter = std::vec::IntoIter<Self::Item>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.sessions.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl Occupant {
|
||||
fn iter(&self) -> std::slice::Iter<'_, Session> {
|
||||
self.sessions.iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::component::TestComponent;
|
||||
use crate::tests::templates::{
|
||||
LOUISE_FULL1, LOUISE_FULL2, LOUISE_NICK, LOUISE_ROOM1_PART, ROOM1_BARE, ROSA_FULL1,
|
||||
ROSA_NICK, ROSA_ROOM1_PART, SUGAKO_FULL1, SUGAKO_NICK, SUGAKO_ROOM1_PART,
|
||||
LOUISE_FULL1, LOUISE_NICK, LOUISE_ROOM1_PART, ROOM1_BARE, ROSA_FULL1, ROSA_NICK,
|
||||
ROSA_ROOM1_PART, SUGAKO_FULL1, SUGAKO_NICK, SUGAKO_ROOM1_PART,
|
||||
};
|
||||
use std::str::FromStr;
|
||||
use xmpp_parsers::{
|
||||
muc::{
|
||||
user::{Affiliation, Item as MucItem, Role, Status as MucStatus},
|
||||
Muc, MucUser,
|
||||
MucUser,
|
||||
},
|
||||
presence::{Presence, Show as PresenceShow, Type as PresenceType},
|
||||
BareJid, Element,
|
||||
presence::{Presence, Type as PresenceType},
|
||||
BareJid, Element, FullJid,
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
|
@ -915,65 +754,4 @@ mod tests {
|
|||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_occupant_update_presence() {
|
||||
let presence_louise1 = Presence::new(PresenceType::None)
|
||||
.with_from(LOUISE_FULL1.clone())
|
||||
.with_to(LOUISE_ROOM1_PART.clone());
|
||||
let presence_louise2 = Presence::new(PresenceType::None)
|
||||
.with_from(LOUISE_FULL2.clone())
|
||||
.with_to(LOUISE_ROOM1_PART.clone());
|
||||
|
||||
let mut occupant = Occupant::new(presence_louise1).unwrap();
|
||||
occupant.add_session(presence_louise2).unwrap();
|
||||
|
||||
let presence1 = Presence::new(PresenceType::None)
|
||||
.with_from(LOUISE_FULL1.clone())
|
||||
.with_to(ROOM1_BARE.clone())
|
||||
.with_show(PresenceShow::Away)
|
||||
.with_payloads(vec![
|
||||
Muc::new().into(),
|
||||
MucUser {
|
||||
status: Vec::new(),
|
||||
items: vec![MucItem::new(Affiliation::Owner, Role::Moderator)],
|
||||
}
|
||||
.into(),
|
||||
]);
|
||||
match occupant.update_presence(presence1.clone()) {
|
||||
Ok(()) => (),
|
||||
err => panic!("Err: {:?}", err),
|
||||
}
|
||||
assert_eq!(occupant.sessions[0].presence, presence1);
|
||||
|
||||
let presence2 = Presence::new(PresenceType::None)
|
||||
.with_from(LOUISE_FULL2.clone())
|
||||
.with_to(ROOM1_BARE.clone())
|
||||
.with_show(PresenceShow::Xa);
|
||||
|
||||
match occupant.update_presence(presence2.clone()) {
|
||||
Err(Error::SecondarySession(session)) if session.real == *LOUISE_FULL2 => (),
|
||||
err => panic!(
|
||||
"Should return Error::SecondarySession(Session {{ {:?} }}), returned: {:?}",
|
||||
*LOUISE_FULL2, err,
|
||||
),
|
||||
}
|
||||
assert_eq!(
|
||||
occupant.sessions[1],
|
||||
Session::with_nick(presence2.clone(), LOUISE_NICK.clone()).unwrap()
|
||||
);
|
||||
|
||||
let presence_leave_louise2 = Presence::new(PresenceType::Unavailable)
|
||||
.with_from(LOUISE_FULL2.clone())
|
||||
.with_to(LOUISE_ROOM1_PART.clone());
|
||||
occupant.remove_session(presence_leave_louise2).unwrap();
|
||||
|
||||
match occupant.update_presence(presence2) {
|
||||
Err(Error::NonexistantSession(session)) if session.real == *LOUISE_FULL2 => (),
|
||||
err => panic!(
|
||||
"Should return Error::SecondarySession(Session {{ {:?} }}), returned: {:?}",
|
||||
*LOUISE_FULL2, err,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
81
src/session.rs
Normal file
81
src/session.rs
Normal file
|
@ -0,0 +1,81 @@
|
|||
// Copyright (C) 2022-2099 The crate authors.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Affero General Public License as published by the
|
||||
// Free Software Foundation, either version 3 of the License, or (at your
|
||||
// option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
|
||||
// for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::error::Error;
|
||||
|
||||
use xmpp_parsers::{presence::Presence, BareJid, FullJid};
|
||||
|
||||
pub type Nick = String;
|
||||
|
||||
/// An occupant session
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Session {
|
||||
pub presence: Presence,
|
||||
pub real: FullJid,
|
||||
pub participant: FullJid,
|
||||
}
|
||||
|
||||
impl Session {
|
||||
/// Ensure presence doesn't contain payloads that would impersonate us
|
||||
fn filter_presence(presence: Presence) -> Presence {
|
||||
presence
|
||||
}
|
||||
|
||||
/// Instanciate a Session from a presence which is adressed to the room JID.
|
||||
pub fn with_nick<N: Into<Nick>>(presence: Presence, nick: N) -> Result<Session, Error> {
|
||||
let presence = Session::filter_presence(presence);
|
||||
Ok(Session {
|
||||
real: presence
|
||||
.from
|
||||
.clone()
|
||||
.map(FullJid::try_from)
|
||||
.ok_or(Error::MissingJid)??,
|
||||
participant: presence
|
||||
.to
|
||||
.clone()
|
||||
.map(BareJid::from)
|
||||
.ok_or(Error::MissingJid)?
|
||||
.with_resource(nick.into()),
|
||||
presence,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Session {
|
||||
fn eq(&self, other: &Session) -> bool {
|
||||
self.real == other.real && self.participant == other.participant
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Presence> for Session {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(presence: Presence) -> Result<Session, Error> {
|
||||
let presence = Session::filter_presence(presence);
|
||||
Ok(Session {
|
||||
real: presence
|
||||
.from
|
||||
.clone()
|
||||
.map(FullJid::try_from)
|
||||
.ok_or(Error::MissingJid)??,
|
||||
participant: presence
|
||||
.to
|
||||
.clone()
|
||||
.map(FullJid::try_from)
|
||||
.ok_or(Error::MissingJid)??,
|
||||
presence,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -13,7 +13,8 @@
|
|||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::room::{Occupant, Room};
|
||||
use crate::occupant::Occupant;
|
||||
use crate::room::Room;
|
||||
use std::str::FromStr;
|
||||
use std::sync::LazyLock;
|
||||
use xmpp_parsers::{
|
||||
|
|
Loading…
Reference in a new issue