tauri_plugin_matrix_svelte/matrix/
rooms.rs1use std::sync::Arc;
2
3use crate::matrix::{
4 events::get_latest_event_details,
5 room::rooms_list::{enqueue_rooms_list_update, JoinedRoomInfo, RoomsListUpdate},
6 singletons::{ALL_JOINED_ROOMS, TOMBSTONED_ROOMS},
7 timeline::{timeline_subscriber_handler, update_latest_event},
8};
9use anyhow::bail;
10use matrix_sdk::{
11 event_handler::EventHandlerDropGuard, ruma::OwnedRoomId, RoomDisplayName, RoomState,
12};
13use matrix_sdk_ui::{room_list_service, RoomListService, Timeline};
14use serde::Serialize;
15use tokio::{runtime::Handle, sync::watch, task::JoinHandle};
16
17use super::{
18 singletons::LOG_ROOM_LIST_DIFFS,
19 timeline::{TimelineRequestSender, TimelineUpdate},
20};
21
22pub struct JoinedRoomDetails {
24 #[allow(unused)]
25 room_id: OwnedRoomId,
26 pub timeline: Arc<Timeline>,
28 pub timeline_update_sender: crossbeam_channel::Sender<TimelineUpdate>,
30 pub timeline_singleton_endpoints: Option<(
41 crossbeam_channel::Receiver<TimelineUpdate>,
42 TimelineRequestSender,
43 )>,
44 timeline_subscriber_handler_task: JoinHandle<()>,
46 pub typing_notice_subscriber: Option<EventHandlerDropGuard>,
48 replaces_tombstoned_room: Option<OwnedRoomId>,
50}
51impl Drop for JoinedRoomDetails {
52 fn drop(&mut self) {
54 println!("Dropping RoomInfo for room {}", self.room_id);
55 self.timeline_subscriber_handler_task.abort();
56 drop(self.typing_notice_subscriber.take());
57 if let Some(replaces_tombstoned_room) = self.replaces_tombstoned_room.take() {
58 TOMBSTONED_ROOMS
59 .lock()
60 .unwrap()
61 .insert(self.room_id.clone(), replaces_tombstoned_room);
62 }
63 }
64}
65
66#[derive(Clone)]
75pub struct RoomListServiceRoomInfo {
76 room: room_list_service::Room,
77 pub room_id: OwnedRoomId,
78 room_state: RoomState,
79}
80impl From<&room_list_service::Room> for RoomListServiceRoomInfo {
81 fn from(room: &room_list_service::Room) -> Self {
82 room.clone().into()
83 }
84}
85impl From<room_list_service::Room> for RoomListServiceRoomInfo {
86 fn from(room: room_list_service::Room) -> Self {
87 Self {
88 room_id: room.room_id().to_owned(),
89 room_state: room.state(),
90 room,
91 }
92 }
93}
94
95#[derive(Clone, Debug)]
97pub enum UnreadMessageCount {
98 Unknown,
100 Known(u64),
102}
103
104#[derive(Debug, Clone, Serialize)]
105#[serde(rename_all = "camelCase")]
106pub struct FrontendRoom {
107 id: String,
108 name: RoomDisplayName,
109 avatar: Option<Vec<u8>>,
110 highlight_count: u64,
111 notification_count: u64,
112 latest_message: String,
113}
114
115pub async fn add_new_room(
116 room: &room_list_service::Room,
117 room_list_service: &RoomListService,
118) -> anyhow::Result<()> {
119 let room_id = room.room_id().to_owned();
120 let room_name = room.display_name().await.map(|n| n.to_string()).ok();
122
123 match room.state() {
124 RoomState::Knocked => {
125 return Ok(());
127 }
128 RoomState::Banned => {
129 println!("Got new Banned room: {room_name:?} ({room_id})");
130 return Ok(());
132 }
133 RoomState::Left => {
134 println!("Got new Left room: {room_name:?} ({room_id})");
135 return Ok(());
143 }
144 RoomState::Joined => {} _ => bail!("We do not handle invited rooms yet"),
185 }
186
187 room_list_service.subscribe_to_rooms(&[&room_id]);
189
190 if let Some(tombstoned_info) = room.tombstone() {
192 println!("Room {room_id} has been tombstoned: {tombstoned_info:#?}");
193 let replacement_room_id = tombstoned_info.replacement_room;
199 if let Some(room_info) = ALL_JOINED_ROOMS
200 .lock()
201 .unwrap()
202 .get_mut(&replacement_room_id)
203 {
204 room_info.replaces_tombstoned_room = Some(replacement_room_id.clone());
205 }
206 else {
210 TOMBSTONED_ROOMS
211 .lock()
212 .unwrap()
213 .insert(replacement_room_id, room_id.clone());
214 }
215 return Ok(());
216 }
217
218 let timeline = if let Some(tl_arc) = room.timeline() {
219 tl_arc
220 } else {
221 let builder = room
222 .default_room_timeline_builder()
223 .await?
224 .track_read_marker_and_receipts();
225 room.init_timeline_with_builder(builder).await?;
226 room.timeline()
227 .ok_or_else(|| anyhow::anyhow!("BUG: room timeline not found for room {room_id}"))?
228 };
229 let latest_event = timeline.latest_event().await;
230 let (timeline_update_sender, timeline_update_receiver) = crossbeam_channel::unbounded();
231
232 let (request_sender, request_receiver) = watch::channel(Vec::new());
233 let timeline_subscriber_handler_task = Handle::current().spawn(timeline_subscriber_handler(
234 room.inner_room().clone(),
235 timeline.clone(),
236 timeline_update_sender.clone(),
237 request_receiver,
238 ));
239
240 let latest = latest_event
241 .as_ref()
242 .map(|ev| get_latest_event_details(ev, &room_id));
243
244 let tombstoned_room_replaced_by_this_room = TOMBSTONED_ROOMS.lock().unwrap().remove(&room_id);
245
246 println!("Adding new joined room {room_id}. Replaces tombstoned room: {tombstoned_room_replaced_by_this_room:?}");
247 ALL_JOINED_ROOMS.lock().unwrap().insert(
248 room_id.clone(),
249 JoinedRoomDetails {
250 room_id: room_id.clone(),
251 timeline,
252 timeline_singleton_endpoints: Some((timeline_update_receiver, request_sender)),
253 timeline_update_sender,
254 timeline_subscriber_handler_task,
255 typing_notice_subscriber: None,
256 replaces_tombstoned_room: tombstoned_room_replaced_by_this_room,
257 },
258 );
259
260 enqueue_rooms_list_update(RoomsListUpdate::AddJoinedRoom(JoinedRoomInfo {
264 room_id,
265 latest,
266 tags: room.tags().await.ok().flatten().unwrap_or_default(),
267 num_unread_messages: room.num_unread_messages(),
268 num_unread_mentions: room.num_unread_mentions(),
269 room_name,
272 canonical_alias: room.canonical_alias(),
273 alt_aliases: room.alt_aliases(),
274 has_been_paginated: false,
275 is_selected: false,
276 }));
277
278 Ok(())
281}
282
283pub async fn update_room(
285 old_room: &RoomListServiceRoomInfo,
286 new_room: &room_list_service::Room,
287 room_list_service: &RoomListService,
288) -> anyhow::Result<()> {
289 let new_room_id = new_room.room_id().to_owned();
290 if old_room.room_id == new_room_id {
291 let new_room_name = new_room.display_name().await.map(|n| n.to_string()).ok();
292 let mut room_avatar_changed = false;
293
294 let old_room_state = old_room.room_state;
296 let new_room_state = new_room.state();
297 if old_room_state != new_room_state {
298 if LOG_ROOM_LIST_DIFFS {
299 println!("Room {new_room_name:?} ({new_room_id}) changed from {old_room_state:?} to {new_room_state:?}");
300 }
301 match new_room_state {
302 RoomState::Banned => {
303 println!("Removing Banned room: {new_room_name:?} ({new_room_id})");
305 remove_room(&new_room.into());
306 return Ok(());
307 }
308 RoomState::Left => {
309 println!("Removing Left room: {new_room_name:?} ({new_room_id})");
310 remove_room(&new_room.into());
311 return Ok(());
319 }
320 RoomState::Joined => {
321 println!(
322 "update_room(): adding new Joined room: {new_room_name:?} ({new_room_id})"
323 );
324 return add_new_room(new_room, room_list_service).await;
325 }
326 RoomState::Invited => {
327 println!(
328 "update_room(): adding new Invited room: {new_room_name:?} ({new_room_id})"
329 );
330 return add_new_room(new_room, room_list_service).await;
331 }
332 RoomState::Knocked => {
333 return Ok(());
335 }
336 }
337 }
338
339 if let Some(new_latest_event) = new_room.latest_event().await {
340 if let Some(old_latest_event) = old_room.room.latest_event().await {
341 if new_latest_event.timestamp() > old_latest_event.timestamp() {
342 println!("Updating latest event for room {}", new_room_id);
343 room_avatar_changed =
344 update_latest_event(new_room_id.clone(), &new_latest_event, None);
345 }
346 }
347 }
348
349 if room_avatar_changed || (old_room.room.avatar_url() != new_room.avatar_url()) {
350 println!("Updating avatar for room {}", new_room_id);
351 }
353
354 if let Some(new_room_name) = new_room_name {
355 if old_room.room.cached_display_name().as_ref() != Some(&new_room_name) {
356 println!(
357 "Updating room name for room {} to {}",
358 new_room_id, new_room_name
359 );
360 enqueue_rooms_list_update(RoomsListUpdate::UpdateRoomName {
361 room_id: new_room_id.clone(),
362 new_room_name,
363 });
364 }
365 }
366
367 if let Ok(new_tags) = new_room.tags().await {
368 enqueue_rooms_list_update(RoomsListUpdate::Tags {
369 room_id: new_room_id.clone(),
370 new_tags: new_tags.unwrap_or_default(),
371 });
372 }
373
374 enqueue_rooms_list_update(RoomsListUpdate::UpdateNumUnreadMessages {
375 room_id: new_room_id.clone(),
376 count: UnreadMessageCount::Known(new_room.num_unread_messages()),
377 unread_mentions: new_room.num_unread_mentions(),
378 });
379
380 Ok(())
381 } else {
382 println!(
383 "UNTESTED SCENARIO: update_room(): removing old room {}, replacing with new room {}",
384 old_room.room_id, new_room_id,
385 );
386 remove_room(old_room);
387 add_new_room(new_room, room_list_service).await
388 }
389}
390
391pub fn remove_room(room: &RoomListServiceRoomInfo) {
393 ALL_JOINED_ROOMS.lock().unwrap().remove(&room.room_id);
394 enqueue_rooms_list_update(RoomsListUpdate::RemoveRoom {
395 room_id: room.room_id.clone(),
396 new_state: room.room_state,
397 });
398}