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}