use std::{ cell::RefCell, collections::HashMap, fmt::{Debug, Formatter}, rc::Rc, }; use async_trait::async_trait; use futures::future::{join, join_all}; use matrix_sdk::{ruma::OwnedRoomId, RoomState as MatrixRoomState}; use tracing::{debug, debug_span, error, instrument, trace}; use super::{ common::{Avatar, UserId}, messaging_interface::{RoomMessagingConsumerInterface, RoomMessagingProviderInterface}, room_member::RoomMember, space::SpaceId, store_interface::{RoomStoreConsumerInterface, RoomStoreProviderInterface}, }; use crate::infrastructure::services::mozaik_builder::create_mozaik; pub type RoomId = OwnedRoomId; #[derive(Clone, PartialEq)] pub struct Invitation { invitee_id: UserId, sender_id: UserId, is_account_user: bool, } impl Invitation { pub fn new(invitee_id: UserId, sender_id: UserId, is_account_user: bool) -> Self { Self { invitee_id, sender_id, is_account_user, } } pub fn is_account_user(&self) -> bool { self.is_account_user } } impl Debug for Invitation { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { f.debug_tuple("Invitation") .field(&self.invitee_id) .field(&self.sender_id) .field(&self.is_account_user) .finish() } } pub struct Room { id: RoomId, name: RefCell>, topic: Option, is_direct: Option, state: Option, avatar: RefCell>, invitations: RefCell>, members: RefCell>, spaces: Vec, messaging_provider: Option>, store: RefCell>>, } impl PartialEq for Room { fn eq(&self, other: &Self) -> bool { self.id == other.id } } impl Room { pub fn new( id: RoomId, // TODO: move space at the end of the list of params name: Option, topic: Option, is_direct: Option, state: Option, spaces: Vec, ) -> Self { Self { id, name: RefCell::new(name), topic, is_direct, state, avatar: RefCell::new(None), invitations: RefCell::new(HashMap::new()), members: RefCell::new(HashMap::new()), spaces, messaging_provider: None, store: RefCell::new(None), } } pub fn set_messaging_provider( &mut self, messaging_provider: Rc, ) { self.messaging_provider = Some(messaging_provider); } pub fn set_store(&self, store: Rc) { *self.store.borrow_mut() = Some(store); } pub fn id(&self) -> &RoomId { &self.id } #[allow(dead_code)] pub fn name(&self) -> Option { self.name.borrow().clone() } #[allow(dead_code)] pub fn set_topic(&mut self, topic: Option) { self.topic = topic; } #[allow(dead_code)] pub fn state(&self) -> &Option { &self.state } #[allow(dead_code)] pub fn is_invited(&self) -> Option { self.state.map(|state| state == MatrixRoomState::Invited) } #[instrument(name = "Room", skip_all)] fn add_invitation(&self, invitation: Invitation) { self.members.borrow_mut().remove(&invitation.invitee_id); self.invitations .borrow_mut() .insert(invitation.invitee_id.clone(), invitation.clone()); if let Some(store) = self.store.borrow().as_ref() { store.on_invitation(invitation); } } #[instrument(name = "Room", skip_all)] fn add_member(&self, member: RoomMember) { let mut members = self.members.borrow_mut(); members.insert(member.id().clone(), member.clone()); // USe the member display name to name the room if it's direct and has no name set. if self.name.borrow().is_none() && members.len() == 1 { if let Some(member_display_name) = member.display_name() { let name = Some(member_display_name.clone()); self.name.borrow_mut().clone_from(&name); if let Some(store) = self.store.borrow().as_ref() { store.on_new_name(name); } } } if let Some(store) = self.store.borrow().as_ref() { store.on_new_member(member); } } pub async fn get_avatar(&self) -> Option { if self.avatar.borrow().is_none() { if let Some(requester) = &self.messaging_provider { let resp = requester.get_avatar(&self.id).await; if let Ok(avatar) = resp { if let Some(avatar) = avatar { return Some(avatar); } else { debug!("The room has no avatar... let's generate one"); match self.gen_room_avatar_with_members().await { Ok(avatar) => { if let Some(avatar) = avatar { return Some(avatar); } } Err(err) => { error!("err={}", err); } } } } else { error!("err={:?}", resp); } } } self.avatar.borrow().clone() } #[instrument(name = "Room", skip_all)] async fn gen_room_avatar_with_members(&self) -> anyhow::Result> { let mut account_member = None::<&RoomMember>; let mut other_members = Vec::<&RoomMember>::new(); let members = self.members.borrow(); for member in members.values() { if member.is_account_user() { account_member = Some(member); } else { other_members.push(member); } } let other_avatars_futures = join_all(other_members.iter().map(|member| member.get_avatar())); let (other_avatars, account_avatar) = if let Some(account_member) = account_member { join(other_avatars_futures, account_member.get_avatar()).await } else { ( join_all(other_members.iter().map(|member| member.get_avatar())).await, None, ) }; let other_avatars: Vec> = other_avatars.into_iter().flatten().collect(); if account_avatar.is_some() || !other_avatars.is_empty() { let _guard = debug_span!("AvatarRendering").entered(); Ok(Some( create_mozaik(256, 256, other_avatars, account_avatar).await, )) } else { Ok(None) } } } #[async_trait(?Send)] impl RoomMessagingConsumerInterface for Room { #[instrument(name = "Room", skip_all)] async fn on_invitation(&self, invitation: Invitation) { trace!("on_invitation"); let sender_id = invitation.sender_id.clone(); self.add_invitation(invitation); if self.is_direct.unwrap_or(false) { debug!("1to1 conversation, using the {} avatar", &sender_id); if let Ok(avatar) = self.gen_room_avatar_with_members().await { debug!("Avatar successfully generated"); self.avatar.borrow_mut().clone_from(&avatar); if let Some(store) = self.store.borrow().as_ref() { store.on_new_avatar(avatar); } } } } #[instrument(name = "Room", skip_all)] async fn on_membership(&self, member: RoomMember) { trace!("on_membership"); self.add_member(member); } #[instrument(name = "Room", skip_all)] async fn on_new_topic(&self, _topic: Option) { trace!("on_new_topic"); } #[instrument(name = "Room", skip_all)] async fn on_new_name(&self, _name: Option) { trace!("on_new_name"); } #[instrument(name = "Room", skip_all)] async fn on_new_avatar(&self, avatar: Option) { trace!("on_new_avatar"); self.avatar.borrow_mut().clone_from(&avatar); if let Some(store) = self.store.borrow().as_ref() { store.on_new_avatar(avatar); } } } #[async_trait(?Send)] impl RoomStoreConsumerInterface for Room { fn id(&self) -> &RoomId { &self.id } fn is_direct(&self) -> Option { self.is_direct } fn name(&self) -> Option { self.name.borrow().clone() } fn topic(&self) -> Option { self.topic.clone() } async fn avatar(&self) -> Option { self.get_avatar().await } fn spaces(&self) -> &Vec { &self.spaces } async fn join(&self) { if let Some(messaging_provider) = &self.messaging_provider { let _ = messaging_provider.join(&self.id).await; } } }