diff --git a/src/session/model/notifications/mod.rs b/src/session/model/notifications/mod.rs index 456425cd..92c09719 100644 --- a/src/session/model/notifications/mod.rs +++ b/src/session/model/notifications/mod.rs @@ -129,7 +129,7 @@ impl Notifications { } }; - let matrix_room = room.matrix_room(); + let matrix_room = room.matrix_room().clone(); let sender_id = event.sender(); let owned_sender_id = sender_id.to_owned(); let handle = diff --git a/src/session/model/room/member_list.rs b/src/session/model/room/member_list.rs index 5b357b37..8606572c 100644 --- a/src/session/model/room/member_list.rs +++ b/src/session/model/room/member_list.rs @@ -149,6 +149,7 @@ impl MemberList { } // We don't have everything locally, request the rest from the server. + let matrix_room = matrix_room.clone(); let handle = spawn_tokio!(async move { let mut memberships = RoomMemberships::all(); memberships.remove(RoomMemberships::LEAVE); diff --git a/src/session/model/room/mod.rs b/src/session/model/room/mod.rs index 063af95c..28ee27d1 100644 --- a/src/session/model/room/mod.rs +++ b/src/session/model/room/mod.rs @@ -69,10 +69,8 @@ mod imp { #[derive(Default, glib::Properties)] #[properties(wrapper_type = super::Room)] pub struct Room { - /// The ID of this room. - #[property(set = Self::set_room_id, construct_only, type = String)] - pub room_id: OnceCell, - pub matrix_room: RefCell>, + /// The room API of the SDK. + pub matrix_room: OnceCell, /// The current session. #[property(get, construct_only)] pub session: glib::WeakRef, @@ -184,7 +182,6 @@ mod imp { return; }; - obj.set_matrix_room(session.client().get_room(obj.room_id()).unwrap()); self.timeline.set(Timeline::new(&obj)).unwrap(); self.timeline @@ -192,7 +189,9 @@ mod imp { .unwrap() .sdk_items() .connect_items_changed(clone!(@weak obj => move |_, _, _, _| { - obj.update_is_read(); + spawn!(clone!(@weak obj => async move { + obj.update_is_read().await; + })); })); // Initialize the avatar first since loading is async. @@ -232,9 +231,9 @@ mod imp { impl SidebarItemImpl for Room {} impl Room { - /// Set the ID of this room. - fn set_room_id(&self, room_id: String) { - self.room_id.set(RoomId::parse(room_id).unwrap()).unwrap(); + /// The room API of the SDK. + pub fn matrix_room(&self) -> &MatrixRoom { + self.matrix_room.get().unwrap() } /// The name of this room. @@ -242,7 +241,7 @@ mod imp { /// This can be empty, the display name should be used instead in the /// interface. fn name(&self) -> Option { - self.matrix_room.borrow().as_ref().unwrap().name() + self.matrix_room().name() } /// The display name of this room. @@ -254,29 +253,21 @@ mod imp { /// The number of unread notifications of this room. fn notification_count(&self) -> u64 { - self.matrix_room - .borrow() - .as_ref() - .unwrap() + self.matrix_room() .unread_notification_counts() .notification_count } /// The topic of this room. fn topic(&self) -> Option { - self.matrix_room - .borrow() - .as_ref() - .unwrap() - .topic() - .filter(|topic| { - !topic.is_empty() && topic.find(|c: char| !c.is_whitespace()).is_some() - }) + self.matrix_room().topic().filter(|topic| { + !topic.is_empty() && topic.find(|c: char| !c.is_whitespace()).is_some() + }) } /// Whether this room was tombstoned. fn is_tombstoned(&self) -> bool { - self.matrix_room.borrow().as_ref().unwrap().is_tombstoned() + self.matrix_room().is_tombstoned() } /// The ID of the successor of this Room, if this room was upgraded. @@ -304,13 +295,14 @@ glib::wrapper! { } impl Room { - pub fn new(session: &Session, room_id: &RoomId, metainfo: Option<&RoomMetainfo>) -> Self { + pub fn new(session: &Session, matrix_room: MatrixRoom, metainfo: Option) -> Self { let this = glib::Object::builder::() .property("session", session) - .property("room-id", &room_id.to_string()) .build(); - if let Some(&RoomMetainfo { + this.set_matrix_room(matrix_room); + + if let Some(RoomMetainfo { latest_activity, is_read, }) = metainfo @@ -324,23 +316,19 @@ impl Room { this } - /// The ID of this room. - pub fn room_id(&self) -> &RoomId { - self.imp().room_id.get().unwrap() + /// The room API of the SDK. + pub fn matrix_room(&self) -> &MatrixRoom { + self.imp().matrix_room() } - pub fn matrix_room(&self) -> MatrixRoom { - self.imp().matrix_room.borrow().as_ref().unwrap().clone() - } - - /// Set the new sdk room struct represented by this `Room` + /// Set the room API of the SDK. fn set_matrix_room(&self, matrix_room: MatrixRoom) { let imp = self.imp(); self.set_joined_members_count(matrix_room.joined_members_count()); self.set_is_join_rule_public(matrix_room.join_rule() == JoinRule::Public); - imp.matrix_room.replace(Some(matrix_room)); + imp.matrix_room.set(matrix_room).unwrap(); self.load_display_name(); self.load_predecessor(); @@ -362,6 +350,11 @@ impl Room { })); } + /// The ID of this room. + pub fn room_id(&self) -> &RoomId { + self.matrix_room().room_id() + } + /// The state of the room. pub fn state(&self) -> RoomState { self.matrix_room().state() @@ -378,7 +371,7 @@ impl Room { } pub async fn load_is_direct(&self) { - let matrix_room = self.matrix_room(); + let matrix_room = self.matrix_room().clone(); let handle = spawn_tokio!(async move { matrix_room.is_direct().await }); match handle.await.unwrap() { @@ -441,8 +434,7 @@ impl Room { return Ok(()); } - let matrix_room = self.matrix_room(); - + let matrix_room = self.matrix_room().clone(); let handle = spawn_tokio!(async move { matrix_room.forget().await }); match handle.await.unwrap() { @@ -490,7 +482,6 @@ impl Room { /// Note: Rooms can't be moved to the invite category and they can't be /// moved once they are upgraded. pub async fn set_category(&self, category: RoomType) -> MatrixResult<()> { - let matrix_room = self.matrix_room(); let previous_category = self.category(); if previous_category == category { @@ -517,6 +508,7 @@ impl Room { self.set_category_internal(category); + let matrix_room = self.matrix_room().clone(); let handle = spawn_tokio!(async move { match matrix_room.state() { RoomState::Invited => match category { @@ -653,7 +645,7 @@ impl Room { match handle.await.unwrap() { Ok(_) => Ok(()), Err(error) => { - error!("Couldn’t set the room category: {error}"); + error!("Could not set the room category: {error}"); // Load the previous category self.load_category(); @@ -670,12 +662,12 @@ impl Room { } let matrix_room = self.matrix_room(); - match matrix_room.state() { RoomState::Joined => { if matrix_room.is_space() { self.set_category_internal(RoomType::Space); } else { + let matrix_room = matrix_room.clone(); let tags = spawn_tokio!(async move { matrix_room.tags().await }); spawn!( @@ -778,7 +770,9 @@ impl Room { for (_event_id, receipts) in content.iter() { if let Some(users) = receipts.get(&ReceiptType::Read) { if users.contains_key(own_user_id) { - self.update_is_read(); + spawn!(clone!(@weak self as obj => async move { + obj.update_is_read().await; + })); } } } @@ -843,13 +837,7 @@ impl Room { return; } - let counts = self - .imp() - .matrix_room - .borrow() - .as_ref() - .unwrap() - .unread_notification_counts(); + let counts = self.matrix_room().unread_notification_counts(); if counts.highlight_count > 0 { highlight = HighlightFlags::all(); @@ -870,14 +858,12 @@ impl Room { self.notify_highlight(); } - fn update_is_read(&self) { - spawn!(clone!(@weak self as obj => async move { - if let Some(has_unread) = obj.timeline().has_unread_messages().await { - obj.set_is_read(!has_unread); - } + async fn update_is_read(&self) { + if let Some(has_unread) = self.timeline().has_unread_messages().await { + self.set_is_read(!has_unread); + } - obj.update_highlight(); - })); + self.update_highlight(); } /// Set whether all messages of this room are read. @@ -901,7 +887,7 @@ impl Room { } fn load_display_name(&self) { - let matrix_room = self.matrix_room(); + let matrix_room = self.matrix_room().clone(); let handle = spawn_tokio!(async move { matrix_room.display_name().await }); spawn!( @@ -963,6 +949,7 @@ impl Room { }; let inviter_id_clone = inviter_id.clone(); + let matrix_room = matrix_room.clone(); let handle = spawn_tokio!(async move { matrix_room.get_member_no_sync(&inviter_id_clone).await }); @@ -1052,7 +1039,7 @@ impl Room { } fn load_power_levels(&self) { - let matrix_room = self.matrix_room(); + let matrix_room = self.matrix_room().clone(); let handle = spawn_tokio!(async move { let state_event = match matrix_room .get_state_event_static::() @@ -1100,7 +1087,7 @@ impl Room { /// Send a `key` reaction for the `relates_to` event ID in this room. pub async fn send_reaction(&self, key: String, relates_to: OwnedEventId) -> MatrixResult<()> { - let matrix_room = self.matrix_room(); + let matrix_room = self.matrix_room().clone(); spawn_tokio!(async move { matrix_room @@ -1124,6 +1111,7 @@ impl Room { return Ok(()); }; + let matrix_room = matrix_room.clone(); let handle = spawn_tokio!(async move { matrix_room .redact(&redacted_event_id, reason.as_deref(), None) @@ -1142,6 +1130,7 @@ impl Room { return; }; + let matrix_room = matrix_room.clone(); let handle = spawn_tokio!(async move { matrix_room.typing_notice(is_typing).await }); spawn!( @@ -1175,6 +1164,7 @@ impl Room { return Ok(()); } + let matrix_room = matrix_room.clone(); let handle = spawn_tokio!(async move { matrix_room.join().await }); match handle.await.unwrap() { Ok(_) => Ok(()), @@ -1193,6 +1183,7 @@ impl Room { return Ok(()); } + let matrix_room = matrix_room.clone(); let handle = spawn_tokio!(async move { matrix_room.leave().await }); match handle.await.unwrap() { Ok(_) => Ok(()), @@ -1375,6 +1366,7 @@ impl Room { return; }; + let matrix_room = matrix_room.clone(); let body = body.to_string(); spawn_tokio!(async move { // Needed to hold the thumbnail data until it is sent. @@ -1422,6 +1414,7 @@ impl Room { } let user_ids_clone = user_ids.to_owned(); + let matrix_room = matrix_room.clone(); let handle = spawn_tokio!(async move { let invitations = user_ids_clone .iter() @@ -1485,7 +1478,7 @@ impl Room { } async fn setup_is_encrypted(&self) { - let matrix_room = self.matrix_room(); + let matrix_room = self.matrix_room().clone(); let handle = spawn_tokio!(async move { matrix_room.is_encrypted().await }); if handle @@ -1540,6 +1533,7 @@ impl Room { // We don't have the active member count for invited rooms so process them too. if let Some(session) = self.session() { if avatar_url.is_none() && members_count > 0 && members_count <= 2 { + let matrix_room = matrix_room.clone(); let handle = spawn_tokio!(async move { matrix_room.members(RoomMemberships::ACTIVE).await }); let members = match handle.await.unwrap() { diff --git a/src/session/model/room/timeline/mod.rs b/src/session/model/room/timeline/mod.rs index b4b5a367..6b41f5f6 100644 --- a/src/session/model/room/timeline/mod.rs +++ b/src/session/model/room/timeline/mod.rs @@ -483,10 +483,9 @@ impl Timeline { let Some(room) = self.room() else { return Err(MatrixError::UnknownError("Failed to upgrade Room".into())); }; - let matrix_room = room.matrix_room(); + let matrix_room = room.matrix_room().clone(); let event_id_clone = event_id.clone(); - let handle = - spawn_tokio!(async move { matrix_room.event(event_id_clone.as_ref()).await }); + let handle = spawn_tokio!(async move { matrix_room.event(&event_id_clone).await }); match handle.await.unwrap() { Ok(room_event) => room_event.event.deserialize_as().map_err(Into::into), Err(error) => { @@ -505,64 +504,61 @@ impl Timeline { }; let imp = self.imp(); let room_id = room.room_id().to_owned(); - let matrix_room = room.matrix_room(); + let matrix_room = room.matrix_room().clone(); let matrix_timeline = spawn_tokio!(async move { - Arc::new( - matrix_room - .timeline_builder() - .event_filter(|any, room_version| { - // Make sure we don't try to show events that can't be shown. - if !default_event_filter(any, room_version) { - return false; - } + matrix_room + .timeline_builder() + .event_filter(|any, room_version| { + // Make sure we don't try to show events that can't be shown. + if !default_event_filter(any, room_version) { + return false; + } - // Only show events we want. - match any { - AnySyncTimelineEvent::MessageLike(msg) => match msg { - AnySyncMessageLikeEvent::RoomMessage( - SyncMessageLikeEvent::Original(ev), - ) => { - matches!( - ev.content.msgtype, - MessageType::Audio(_) - | MessageType::Emote(_) - | MessageType::File(_) - | MessageType::Image(_) - | MessageType::Location(_) - | MessageType::Notice(_) - | MessageType::ServerNotice(_) - | MessageType::Text(_) - | MessageType::Video(_) - | MessageType::VerificationRequest(_) - ) - } - AnySyncMessageLikeEvent::Sticker( - SyncMessageLikeEvent::Original(_), + // Only show events we want. + match any { + AnySyncTimelineEvent::MessageLike(msg) => match msg { + AnySyncMessageLikeEvent::RoomMessage( + SyncMessageLikeEvent::Original(ev), + ) => { + matches!( + ev.content.msgtype, + MessageType::Audio(_) + | MessageType::Emote(_) + | MessageType::File(_) + | MessageType::Image(_) + | MessageType::Location(_) + | MessageType::Notice(_) + | MessageType::ServerNotice(_) + | MessageType::Text(_) + | MessageType::Video(_) + | MessageType::VerificationRequest(_) ) - | AnySyncMessageLikeEvent::RoomEncrypted( - SyncMessageLikeEvent::Original(_), - ) => true, - _ => false, - }, - AnySyncTimelineEvent::State(state) => matches!( - state, - AnySyncStateEvent::RoomMember(_) - | AnySyncStateEvent::RoomCreate(_) - | AnySyncStateEvent::RoomEncryption(_) - | AnySyncStateEvent::RoomThirdPartyInvite(_) - | AnySyncStateEvent::RoomTombstone(_) - ), - } - }) - .add_failed_to_parse(false) - .build() - .await, - ) + } + AnySyncMessageLikeEvent::Sticker(SyncMessageLikeEvent::Original(_)) + | AnySyncMessageLikeEvent::RoomEncrypted( + SyncMessageLikeEvent::Original(_), + ) => true, + _ => false, + }, + AnySyncTimelineEvent::State(state) => matches!( + state, + AnySyncStateEvent::RoomMember(_) + | AnySyncStateEvent::RoomCreate(_) + | AnySyncStateEvent::RoomEncryption(_) + | AnySyncStateEvent::RoomThirdPartyInvite(_) + | AnySyncStateEvent::RoomTombstone(_) + ), + } + }) + .add_failed_to_parse(false) + .build() + .await }) .await .unwrap(); + let matrix_timeline = Arc::new(matrix_timeline); imp.timeline.set(matrix_timeline.clone()).unwrap(); let (values, timeline_stream) = matrix_timeline.subscribe().await; diff --git a/src/session/model/room_list/mod.rs b/src/session/model/room_list/mod.rs index 73bf87f8..7192b6d4 100644 --- a/src/session/model/room_list/mod.rs +++ b/src/session/model/room_list/mod.rs @@ -11,7 +11,7 @@ use matrix_sdk::{ RoomMemberships, }; use ruma::UserId; -use tracing::error; +use tracing::{error, warn}; mod room_list_metainfo; @@ -260,16 +260,23 @@ impl RoomList { return; }; let imp = self.imp(); + let client = session.client(); let mut new_rooms = HashMap::new(); for (room_id, left_room) in rooms.leave { let room = match self.get(&room_id) { Some(room) => room, - None => new_rooms - .entry(room_id.clone()) - .or_insert_with_key(|room_id| Room::new(&session, room_id, None)) - .clone(), + None => match client.get_room(&room_id) { + Some(matrix_room) => new_rooms + .entry(room_id.clone()) + .or_insert_with(|| Room::new(&session, matrix_room, None)) + .clone(), + None => { + warn!("Could not find left room {room_id}"); + continue; + } + }, }; self.pending_rooms_remove((*room_id).into()); @@ -280,10 +287,16 @@ impl RoomList { for (room_id, joined_room) in rooms.join { let room = match self.get(&room_id) { Some(room) => room, - None => new_rooms - .entry(room_id.clone()) - .or_insert_with_key(|room_id| Room::new(&session, room_id, None)) - .clone(), + None => match client.get_room(&room_id) { + Some(matrix_room) => new_rooms + .entry(room_id.clone()) + .or_insert_with(|| Room::new(&session, matrix_room, None)) + .clone(), + None => { + warn!("Could not find joined room {room_id}"); + continue; + } + }, }; self.pending_rooms_remove((*room_id).into()); @@ -295,10 +308,16 @@ impl RoomList { for (room_id, _invited_room) in rooms.invite { let room = match self.get(&room_id) { Some(room) => room, - None => new_rooms - .entry(room_id.clone()) - .or_insert_with_key(|room_id| Room::new(&session, room_id, None)) - .clone(), + None => match client.get_room(&room_id) { + Some(matrix_room) => new_rooms + .entry(room_id.clone()) + .or_insert_with(|| Room::new(&session, matrix_room, None)) + .clone(), + None => { + warn!("Could not find invited room {room_id}"); + continue; + } + }, }; self.pending_rooms_remove((*room_id).into()); @@ -404,7 +423,7 @@ impl RoomList { let mut final_rooms = vec![]; for room in direct_rooms { - let matrix_room = room.matrix_room(); + let matrix_room = room.matrix_room().clone(); let handle = spawn_tokio!(async move { matrix_room.members(RoomMemberships::ACTIVE).await }); diff --git a/src/session/model/room_list/room_list_metainfo.rs b/src/session/model/room_list/room_list_metainfo.rs index 5f5c86ca..db80ecd5 100644 --- a/src/session/model/room_list/room_list_metainfo.rs +++ b/src/session/model/room_list/room_list_metainfo.rs @@ -33,15 +33,11 @@ impl RoomListMetainfo { return IndexMap::new(); }; let client = session.client(); - let room_ids = client - .rooms() - .into_iter() - .map(|room| room.room_id().to_owned()) - .collect::>(); // Load the serialized map from the store. + let client_clone = client.clone(); let handle = spawn_tokio!(async move { - client + client_clone .store() .get_custom_value(ROOMS_METAINFO_KEY.as_bytes()) .await @@ -62,26 +58,29 @@ impl RoomListMetainfo { } }; - // Remove unknown rooms. - rooms_metainfo.retain(|room_id, _| room_ids.contains(room_id)); - // We need to acquire the lock now to make sure we have the full map before any // change happens and the map tries to be persisted. let mut rooms_metainfo_guard = self.rooms_metainfo.lock().await; // Restore rooms and listen to changes. - let mut rooms = IndexMap::with_capacity(room_ids.len()); - for room_id in room_ids { - let metainfo = rooms_metainfo.get(&room_id); - let room = Room::new(&session, &room_id, metainfo); + let matrix_rooms = client.rooms(); + let mut rooms = IndexMap::with_capacity(matrix_rooms.len()); + + for matrix_room in matrix_rooms { + let room_id = matrix_room.room_id().to_owned(); + let metainfo = rooms_metainfo.remove(&room_id); + + let room = Room::new(&session, matrix_room, metainfo); self.watch_room(&room); + if let Some(metainfo) = metainfo { + rooms_metainfo_guard.insert(room_id.clone(), metainfo); + } + rooms.insert(room_id, room); } - *rooms_metainfo_guard = rooms_metainfo; - rooms } diff --git a/src/session/model/verification/verification_list.rs b/src/session/model/verification/verification_list.rs index c562bb13..338464b8 100644 --- a/src/session/model/verification/verification_list.rs +++ b/src/session/model/verification/verification_list.rs @@ -241,7 +241,7 @@ impl VerificationList { continue; }; - let matrix_room = room.matrix_room(); + let matrix_room = room.matrix_room().clone(); let owned_user_id_to_verify = user_id_to_verify.clone(); let handle = spawn_tokio!(async move { matrix_room diff --git a/src/session/view/content/room_details/general_page/mod.rs b/src/session/view/content/room_details/general_page/mod.rs index edc6bc49..d7d7f149 100644 --- a/src/session/view/content/room_details/general_page/mod.rs +++ b/src/session/view/content/room_details/general_page/mod.rs @@ -345,6 +345,7 @@ impl GeneralPage { let (action, weak_action) = OngoingAsyncAction::set(uri.to_string()); imp.changing_avatar.replace(Some(action)); + let matrix_room = matrix_room.clone(); let handle = spawn_tokio!(async move { matrix_room.set_avatar_url(&uri, Some(image_info)).await }); @@ -402,6 +403,7 @@ impl GeneralPage { let (action, weak_action) = OngoingAsyncAction::remove(); imp.changing_avatar.replace(Some(action)); + let matrix_room = matrix_room.clone(); let handle = spawn_tokio!(async move { matrix_room.remove_avatar().await }); // We don't need to handle the success of the request, we should receive the diff --git a/src/session/view/content/room_details/history_viewer/timeline.rs b/src/session/view/content/room_details/history_viewer/timeline.rs index ed0c0c9a..64b87cf4 100644 --- a/src/session/view/content/room_details/history_viewer/timeline.rs +++ b/src/session/view/content/room_details/history_viewer/timeline.rs @@ -105,7 +105,7 @@ impl Timeline { self.set_state(TimelineState::Loading); let room = self.room(); - let matrix_room = room.matrix_room(); + let matrix_room = room.matrix_room().clone(); let last_token = imp.last_token.clone(); let is_encrypted = room.encrypted(); let handle: tokio::task::JoinHandle> = spawn_tokio!(async move { diff --git a/src/session/view/content/room_history/item_row.rs b/src/session/view/content/room_history/item_row.rs index eabe7a41..ed4532e6 100644 --- a/src/session/view/content/room_history/item_row.rs +++ b/src/session/view/content/room_history/item_row.rs @@ -424,7 +424,7 @@ impl ItemRow { let Some(room) = event.room() else { return; }; - let matrix_room = room.matrix_room(); + let matrix_room = room.matrix_room().clone(); let event_id = event.event_id().unwrap(); spawn!(clone!(@weak widget => async move { let handle = spawn_tokio!(async move { diff --git a/src/session/view/content/room_history/mod.rs b/src/session/view/content/room_history/mod.rs index 1d98d2c5..3907f5b6 100644 --- a/src/session/view/content/room_history/mod.rs +++ b/src/session/view/content/room_history/mod.rs @@ -526,8 +526,8 @@ impl RoomHistory { pub async fn permalink(&self) { if let Some(room) = self.room() { - let room = room.matrix_room(); - let handle = spawn_tokio!(async move { room.matrix_to_permalink().await }); + let matrix_room = room.matrix_room().clone(); + let handle = spawn_tokio!(async move { matrix_room.matrix_to_permalink().await }); match handle.await.unwrap() { Ok(permalink) => { self.clipboard().set_text(&permalink.to_string()); diff --git a/src/session/view/media_viewer.rs b/src/session/view/media_viewer.rs index 3e6990de..f3d91ca4 100644 --- a/src/session/view/media_viewer.rs +++ b/src/session/view/media_viewer.rs @@ -522,8 +522,8 @@ impl MediaViewer { let Some(event_id) = self.imp().event_id.borrow().clone() else { return; }; - let matrix_room = room.matrix_room(); + let matrix_room = room.matrix_room().clone(); let handle = spawn_tokio!(async move { matrix_room.matrix_to_event_permalink(event_id).await });