matrix_sdk_ui/room_list_service/room.rs
1// Copyright 2023 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for that specific language governing permissions and
13// limitations under the License.
14
15//! The `Room` type.
16
17use core::fmt;
18use std::{ops::Deref, sync::Arc};
19
20use async_once_cell::OnceCell as AsyncOnceCell;
21use matrix_sdk::SlidingSync;
22use ruma::RoomId;
23use tracing::info;
24
25use super::Error;
26use crate::{
27 timeline::{EventTimelineItem, TimelineBuilder},
28 Timeline,
29};
30
31/// A room in the room list.
32///
33/// It's cheap to clone this type.
34#[derive(Clone)]
35pub struct Room {
36 inner: Arc<RoomInner>,
37}
38
39impl fmt::Debug for Room {
40 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
41 formatter.debug_tuple("Room").field(&self.id().to_owned()).finish()
42 }
43}
44
45struct RoomInner {
46 /// The Sliding Sync where everything comes from.
47 sliding_sync: Arc<SlidingSync>,
48
49 /// The underlying client room.
50 room: matrix_sdk::Room,
51
52 /// The timeline of the room.
53 timeline: AsyncOnceCell<Arc<Timeline>>,
54}
55
56impl Deref for Room {
57 type Target = matrix_sdk::Room;
58
59 fn deref(&self) -> &Self::Target {
60 &self.inner.room
61 }
62}
63
64impl Room {
65 /// Create a new `Room`.
66 pub(super) fn new(room: matrix_sdk::Room, sliding_sync: &Arc<SlidingSync>) -> Self {
67 Self {
68 inner: Arc::new(RoomInner {
69 sliding_sync: sliding_sync.clone(),
70 room,
71 timeline: AsyncOnceCell::new(),
72 }),
73 }
74 }
75
76 /// Get the room ID.
77 pub fn id(&self) -> &RoomId {
78 self.inner.room.room_id()
79 }
80
81 /// Get a computed room name for the room.
82 pub fn cached_display_name(&self) -> Option<String> {
83 Some(self.inner.room.cached_display_name()?.to_string())
84 }
85
86 /// Get the underlying [`matrix_sdk::Room`].
87 pub fn inner_room(&self) -> &matrix_sdk::Room {
88 &self.inner.room
89 }
90
91 /// Get the timeline of the room if one exists.
92 pub fn timeline(&self) -> Option<Arc<Timeline>> {
93 self.inner.timeline.get().cloned()
94 }
95
96 /// Get whether the timeline has been already initialised or not.
97 pub fn is_timeline_initialized(&self) -> bool {
98 self.inner.timeline.get().is_some()
99 }
100
101 /// Initialize the timeline of the room with an event type filter so only
102 /// some events are returned. If a previous timeline exists, it'll
103 /// return an error. Otherwise, a Timeline will be returned.
104 pub async fn init_timeline_with_builder(&self, builder: TimelineBuilder) -> Result<(), Error> {
105 if self.inner.timeline.get().is_some() {
106 Err(Error::TimelineAlreadyExists(self.inner.room.room_id().to_owned()))
107 } else {
108 self.inner
109 .timeline
110 .get_or_try_init(async { Ok(Arc::new(builder.build().await?)) })
111 .await
112 .map_err(Error::InitializingTimeline)?;
113 Ok(())
114 }
115 }
116
117 /// Get the latest event in the timeline.
118 ///
119 /// The latest event comes first from the `Timeline`, it can be a local or a
120 /// remote event. Note that the `Timeline` can have more information esp. if
121 /// it has run a backpagination for example. Otherwise if the `Timeline`
122 /// doesn't have any latest event, it comes from the cache. This method
123 /// does not fetch any events or calculate anything — if it's not already
124 /// available, we return `None`.
125 ///
126 /// Reminder: this method also returns `None` is the latest event is not
127 /// suitable for use in a message preview.
128 pub async fn latest_event(&self) -> Option<EventTimelineItem> {
129 // Look for a local event in the `Timeline`.
130 //
131 // First off, let's see if a `Timeline` exists…
132 if let Some(timeline) = self.inner.timeline.get() {
133 // If it contains a `latest_event`…
134 if let Some(timeline_last_event) = timeline.latest_event().await {
135 // If it's a local event or a remote event, we return it.
136 return Some(timeline_last_event);
137 }
138 }
139
140 // Otherwise, fallback to the classical path.
141 if let Some(latest_event) = self.inner.room.latest_event() {
142 EventTimelineItem::from_latest_event(
143 self.inner.room.client(),
144 self.inner.room.room_id(),
145 latest_event,
146 )
147 .await
148 } else {
149 None
150 }
151 }
152
153 /// Create a new [`TimelineBuilder`] with the default configuration.
154 ///
155 /// If the room was synced before some initial events will be added to the
156 /// [`TimelineBuilder`].
157 pub async fn default_room_timeline_builder(&self) -> Result<TimelineBuilder, Error> {
158 // TODO we can remove this once the event cache handles his own cache.
159
160 let sliding_sync_room = self.inner.sliding_sync.get_room(self.inner.room.room_id()).await;
161
162 if let Some(sliding_sync_room) = sliding_sync_room {
163 self.inner
164 .room
165 .client()
166 .event_cache()
167 .add_initial_events(
168 self.inner.room.room_id(),
169 sliding_sync_room.timeline_queue().iter().cloned().collect(),
170 sliding_sync_room.prev_batch(),
171 )
172 .await
173 .map_err(Error::EventCache)?;
174 } else {
175 info!(
176 "No cached sliding sync room found for `{}`, the timeline will be empty.",
177 self.room_id()
178 );
179 }
180
181 Ok(Timeline::builder(&self.inner.room).track_read_marker_and_receipts())
182 }
183}