use super::{
member::{Member, Members},
TimelineEvent,
};
use ahash::AHashMap;
use ruma::{
api::exports::http::Uri, events::room::member::MembershipChange, EventId, RoomAliasId, RoomId,
RoomVersionId, UserId,
};
use std::time::{Duration, Instant};
use uuid::Uuid;
pub type Rooms = AHashMap<RoomId, Room>;
pub struct Room {
version: RoomVersionId,
name: Option<String>,
canonical_alias: Option<RoomAliasId>,
alt_aliases: Vec<RoomAliasId>,
timeline: Vec<TimelineEvent>,
members: Members,
display_name_to_user_id: AHashMap<String, Vec<UserId>>,
wait_for_duration: AHashMap<Uuid, Duration>,
}
impl Default for Room {
fn default() -> Self {
Self {
version: RoomVersionId::Version5,
name: None,
canonical_alias: None,
alt_aliases: Default::default(),
timeline: Default::default(),
members: Default::default(),
display_name_to_user_id: Default::default(),
wait_for_duration: Default::default(),
}
}
}
impl Room {
pub fn new() -> Self {
Self::default()
}
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
}
pub fn canonical_alias(&self) -> Option<&str> {
self.canonical_alias.as_ref().map(|s| s.as_str())
}
pub fn alt_aliases(&self) -> &[RoomAliasId] {
self.alt_aliases.as_slice()
}
pub fn timeline(&self) -> &[TimelineEvent] {
self.timeline.as_slice()
}
pub fn queued_events(&self) -> impl Iterator<Item = &TimelineEvent> + '_ {
self.timeline.iter().filter(|event| !event.is_ack())
}
pub fn displayable_events(&self) -> impl Iterator<Item = &TimelineEvent> + '_ {
self.timeline
.iter()
.filter(|event| event.should_show_to_user())
}
pub fn oldest_event(&self) -> Option<&TimelineEvent> {
self.timeline.first()
}
pub fn newest_event(&self) -> Option<&TimelineEvent> {
self.timeline.last()
}
pub fn members(&self) -> &Members {
&self.members
}
pub fn get_member(&self, user_id: &UserId) -> Option<&Member> {
self.members.get(user_id)
}
pub fn get_member_mut(&mut self, user_id: &UserId) -> Option<&mut Member> {
self.members.get_mut(user_id)
}
pub fn typing_members(&self) -> Vec<&UserId> {
self.members
.iter()
.filter_map(|(id, member)| if member.is_typing() { Some(id) } else { None })
.collect()
}
pub fn get_display_name(&self) -> String {
match &self.name {
Some(name) => name.clone(),
None => match &self.canonical_alias {
Some(alias) => alias.to_string(),
None => match self.members.len() {
x if x > 2 => format!(
"{}, {} and {} others",
self.get_user_display_name(self.members.keys().next().unwrap()),
self.get_user_display_name(self.members.keys().nth(1).unwrap()),
self.members.len() - 2
),
2 => format!(
"{} and {}",
self.get_user_display_name(self.members.keys().next().unwrap()),
self.get_user_display_name(self.members.keys().nth(1).unwrap()),
),
_ => String::from("Empty room"),
},
},
}
}
pub fn get_user_display_name(&self, user_id: &UserId) -> String {
if let Some(member) = self.members.get(user_id) {
if let Some(name) = member.display_name() {
if let Some(ids) = self.display_name_to_user_id.get(name) {
if ids.len() > 1 {
return format!("{} ({})", name, user_id);
}
}
return name.to_string();
}
}
user_id.to_string()
}
pub fn update_typing(&mut self, typing_member_ids: &[UserId], recv_time: Instant) {
for member in self.members.values_mut() {
member.set_typing(None);
}
for member_id in typing_member_ids {
if let Some(member) = self.members.get_mut(&member_id) {
member.set_typing(Some(recv_time))
}
}
}
pub fn update_member(
&mut self,
prev_displayname: Option<String>,
displayname: Option<String>,
avatar_url: Option<Uri>,
membership_change: MembershipChange,
user_id: UserId,
) {
let member = self
.members
.entry(user_id.clone())
.and_modify(|member| {
member.set_display_name(displayname.clone());
member.set_avatar_url(avatar_url.clone());
})
.or_insert_with(move || {
let mut new_member = Member::new();
new_member.set_display_name(displayname);
new_member.set_avatar_url(avatar_url);
new_member
});
match membership_change {
MembershipChange::Left
| MembershipChange::Banned
| MembershipChange::Kicked
| MembershipChange::KickedAndBanned => {
if let Some(name) = member.display_name() {
if let Some(ids) = self.display_name_to_user_id.get_mut(name) {
if let Some(index) = ids.iter().position(|id| id == &user_id) {
ids.remove(index);
}
}
}
member.set_display(false);
}
MembershipChange::Joined | MembershipChange::Unbanned => {
if let Some(name) = member.display_name() {
let ids = self
.display_name_to_user_id
.entry(name.to_string())
.or_default();
ids.push(user_id.clone());
}
member.set_display(true);
}
MembershipChange::ProfileChanged {
displayname_changed,
avatar_url_changed: _,
} => {
if displayname_changed {
if let Some(name) = prev_displayname {
if let Some(ids) = self.display_name_to_user_id.get_mut(&name) {
if let Some(index) = ids.iter().position(|id| id == &user_id) {
ids.remove(index);
}
}
}
if let Some(name) = member.display_name() {
if let Some(ids) = self.display_name_to_user_id.get_mut(name) {
ids.push(user_id);
}
}
}
}
_ => {}
}
}
pub fn add_chunk_of_events(
&mut self,
events_before: Vec<TimelineEvent>,
events_after: Vec<TimelineEvent>,
point_event_id: &EventId,
) {
if let Some(point_index) = self
.timeline
.iter()
.position(|tevent| tevent.id() == point_event_id)
{
let mut point_index_offset = 0;
let mut i = point_index;
for event in events_before {
if let Some(ci) = i.checked_sub(1) {
if event != self.timeline[ci] {
self.timeline.insert(i, event);
point_index_offset += 1;
} else {
i -= 1;
}
} else {
self.timeline.insert(0, event);
point_index_offset += 1;
}
}
i = point_index + point_index_offset;
for event in events_after {
if i + 1 < self.timeline.len() {
if event != self.timeline[i + 1] {
self.timeline.insert(i + 1, event);
} else {
i += 1;
}
} else {
self.timeline.insert(self.timeline.len(), event);
i += 1;
}
}
}
}
pub fn add_event(&mut self, event: TimelineEvent) {
if !event.is_ack() || !self.timeline.contains(&event) {
self.timeline.push(event);
}
}
pub fn redact_event(&mut self, redaction_event: &TimelineEvent) {
if let Some(rid) = redaction_event.redacts() {
if let Some(index) = self.timeline.iter().position(|tevent| tevent.id() == rid) {
let redacted_tevent = self
.timeline
.remove(index)
.redact(redaction_event, self.version.clone());
self.timeline.insert(index, redacted_tevent);
}
}
}
pub fn wait_for_duration(&mut self, duration: Duration, transaction_id: Uuid) {
*self
.wait_for_duration
.entry(transaction_id)
.or_insert(duration) = duration;
}
pub fn get_wait_for_duration(&self, transaction_id: &Uuid) -> Option<Duration> {
self.wait_for_duration.get(transaction_id).copied()
}
pub fn ack_event(&mut self, transaction_id: &Uuid) {
self.wait_for_duration.remove(transaction_id);
if let Some(index) = self
.timeline
.iter()
.position(|tevent| tevent.transaction_id() == Some(transaction_id))
{
self.timeline.remove(index);
}
}
pub fn set_name(&mut self, name: Option<String>) {
self.name = name;
}
pub fn set_canonical_alias(&mut self, canonical_alias: Option<RoomAliasId>) {
self.canonical_alias = canonical_alias;
}
pub fn set_alt_aliases(&mut self, alt_aliases: Vec<RoomAliasId>) {
self.alt_aliases = alt_aliases;
}
}