Skip to content

Commit c252b98

Browse files
authored
refactor(sdk): Use a builder pattern to construct Timeline
`Timeline::with_fully_read_tracking` was public although all public Timeline constructors enable it by default. It is also meant to be called when constructing the timeline so using a builder pattern makes sense. Signed-off-by: Kévin Commaille <[email protected]>
1 parent ee9d47f commit c252b98

File tree

5 files changed

+179
-117
lines changed

5 files changed

+179
-117
lines changed

crates/matrix-sdk/src/room/common.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ impl Common {
270270
/// independent events.
271271
#[cfg(feature = "experimental-timeline")]
272272
pub async fn timeline(&self) -> Timeline {
273-
Timeline::new(self).with_fully_read_tracking().await
273+
Timeline::builder(self).track_fully_read().build().await
274274
}
275275

276276
/// Fetch the event with the given `EventId` in this room.
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
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 the specific language governing permissions and
13+
// limitations under the License.
14+
15+
use std::sync::Arc;
16+
17+
use matrix_sdk_base::{
18+
deserialized_responses::{EncryptionInfo, SyncTimelineEvent},
19+
locks::Mutex,
20+
};
21+
use ruma::events::fully_read::FullyReadEventContent;
22+
use tracing::error;
23+
24+
use super::{
25+
inner::TimelineInner,
26+
to_device::{handle_forwarded_room_key_event, handle_room_key_event},
27+
Timeline,
28+
};
29+
use crate::room;
30+
31+
/// Builder that allows creating and configuring various parts of a
32+
/// [`Timeline`].
33+
#[must_use]
34+
#[derive(Debug)]
35+
pub(crate) struct TimelineBuilder {
36+
room: room::Common,
37+
prev_token: Option<String>,
38+
events: Vec<SyncTimelineEvent>,
39+
track_fully_read: bool,
40+
}
41+
42+
impl TimelineBuilder {
43+
pub(super) fn new(room: &room::Common) -> Self {
44+
Self {
45+
room: room.clone(),
46+
prev_token: None,
47+
events: Vec::default(),
48+
track_fully_read: false,
49+
}
50+
}
51+
52+
/// Add initial events to the timeline.
53+
#[cfg(feature = "experimental-sliding-sync")]
54+
pub(crate) fn events(
55+
mut self,
56+
prev_token: Option<String>,
57+
events: Vec<SyncTimelineEvent>,
58+
) -> Self {
59+
self.prev_token = prev_token;
60+
self.events = events;
61+
self
62+
}
63+
64+
/// Enable tracking of the fully-read marker on the timeline.
65+
pub(crate) fn track_fully_read(mut self) -> Self {
66+
self.track_fully_read = true;
67+
self
68+
}
69+
70+
/// Create a [`Timeline`] with the options set on this builder.
71+
pub(crate) async fn build(self) -> Timeline {
72+
let Self { room, prev_token, events, track_fully_read } = self;
73+
let has_events = !events.is_empty();
74+
75+
let mut inner = TimelineInner::new(room);
76+
77+
if has_events {
78+
inner.add_initial_events(events).await;
79+
}
80+
81+
let inner = Arc::new(inner);
82+
let room = inner.room();
83+
84+
let timeline_event_handle = room.add_event_handler({
85+
let inner = inner.clone();
86+
move |event, encryption_info: Option<EncryptionInfo>| {
87+
let inner = inner.clone();
88+
async move {
89+
inner.handle_live_event(event, encryption_info).await;
90+
}
91+
}
92+
});
93+
94+
// Not using room.add_event_handler here because RoomKey events are
95+
// to-device events that are not received in the context of a room.
96+
#[cfg(feature = "e2e-encryption")]
97+
let room_key_handle = room
98+
.client
99+
.add_event_handler(handle_room_key_event(inner.clone(), room.room_id().to_owned()));
100+
#[cfg(feature = "e2e-encryption")]
101+
let forwarded_room_key_handle = room.client.add_event_handler(
102+
handle_forwarded_room_key_event(inner.clone(), room.room_id().to_owned()),
103+
);
104+
105+
let mut event_handler_handles = vec![
106+
timeline_event_handle,
107+
#[cfg(feature = "e2e-encryption")]
108+
room_key_handle,
109+
#[cfg(feature = "e2e-encryption")]
110+
forwarded_room_key_handle,
111+
];
112+
113+
if track_fully_read {
114+
match room.account_data_static::<FullyReadEventContent>().await {
115+
Ok(Some(fully_read)) => match fully_read.deserialize() {
116+
Ok(fully_read) => {
117+
inner.set_fully_read_event(fully_read.content.event_id).await;
118+
}
119+
Err(e) => {
120+
error!("Failed to deserialize fully-read account data: {e}");
121+
}
122+
},
123+
Err(e) => {
124+
error!("Failed to get fully-read account data from the store: {e}");
125+
}
126+
_ => {}
127+
}
128+
129+
let fully_read_handle = room.add_event_handler({
130+
let inner = inner.clone();
131+
move |event| {
132+
let inner = inner.clone();
133+
async move {
134+
inner.handle_fully_read(event).await;
135+
}
136+
}
137+
});
138+
event_handler_handles.push(fully_read_handle);
139+
}
140+
141+
let timeline = Timeline {
142+
inner,
143+
start_token: Mutex::new(prev_token),
144+
_end_token: Mutex::new(None),
145+
event_handler_handles,
146+
};
147+
148+
#[cfg(feature = "e2e-encryption")]
149+
if has_events {
150+
// The events we're injecting might be encrypted events, but we might
151+
// have received the room key to decrypt them while nobody was listening to the
152+
// `m.room_key` event, let's retry now.
153+
//
154+
// TODO: We could spawn a task here and put this into the background, though it
155+
// might not be worth it depending on the number of events we injected.
156+
// Some measuring needs to be done.
157+
timeline.retry_decryption_for_all_events().await;
158+
}
159+
160+
timeline
161+
}
162+
}

crates/matrix-sdk/src/room/timeline/inner.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,9 @@ use std::{
66
use async_trait::async_trait;
77
use futures_signals::signal_vec::{MutableVec, MutableVecLockRef, SignalVec};
88
use indexmap::IndexSet;
9-
#[cfg(any(test, feature = "experimental-sliding-sync"))]
10-
use matrix_sdk_base::deserialized_responses::SyncTimelineEvent;
119
use matrix_sdk_base::{
1210
crypto::OlmMachine,
13-
deserialized_responses::{EncryptionInfo, TimelineEvent},
11+
deserialized_responses::{EncryptionInfo, SyncTimelineEvent, TimelineEvent},
1412
locks::Mutex,
1513
};
1614
use ruma::{
@@ -75,7 +73,6 @@ impl<P: ProfileProvider> TimelineInner<P> {
7573
self.items.signal_vec_cloned()
7674
}
7775

78-
#[cfg(any(test, feature = "experimental-sliding-sync"))]
7976
pub(super) async fn add_initial_events(&mut self, events: Vec<SyncTimelineEvent>) {
8077
if events.is_empty() {
8178
return;

crates/matrix-sdk/src/room/timeline/mod.rs

Lines changed: 8 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,9 @@ use std::sync::Arc;
2020

2121
use futures_core::Stream;
2222
use futures_signals::signal_vec::{SignalVec, SignalVecExt, VecDiff};
23-
#[cfg(feature = "experimental-sliding-sync")]
24-
use matrix_sdk_base::deserialized_responses::SyncTimelineEvent;
25-
use matrix_sdk_base::{deserialized_responses::EncryptionInfo, locks::Mutex};
23+
use matrix_sdk_base::locks::Mutex;
2624
use ruma::{
27-
assign,
28-
events::{fully_read::FullyReadEventContent, AnyMessageLikeEventContent},
29-
EventId, MilliSecondsSinceUnixEpoch, TransactionId,
25+
assign, events::AnyMessageLikeEventContent, EventId, MilliSecondsSinceUnixEpoch, TransactionId,
3026
};
3127
use thiserror::Error;
3228
use tracing::{error, instrument, warn};
@@ -38,6 +34,7 @@ use crate::{
3834
Result,
3935
};
4036

37+
mod builder;
4138
mod event_handler;
4239
mod event_item;
4340
mod inner;
@@ -48,6 +45,8 @@ mod tests;
4845
mod to_device;
4946
mod virtual_item;
5047

48+
pub(crate) use self::builder::TimelineBuilder;
49+
use self::inner::{TimelineInner, TimelineInnerMetadata};
5150
pub use self::{
5251
event_item::{
5352
AnyOtherFullStateEventContent, BundledReactions, EncryptedMessage, EventSendState,
@@ -58,10 +57,6 @@ pub use self::{
5857
pagination::{PaginationOptions, PaginationOutcome},
5958
virtual_item::VirtualTimelineItem,
6059
};
61-
use self::{
62-
inner::{TimelineInner, TimelineInnerMetadata},
63-
to_device::{handle_forwarded_room_key_event, handle_room_key_event},
64-
};
6560

6661
/// A high-level view into a regular¹ room's contents.
6762
///
@@ -85,107 +80,14 @@ impl Drop for Timeline {
8580
}
8681

8782
impl Timeline {
88-
pub(super) fn new(room: &room::Common) -> Self {
89-
Self::from_inner(Arc::new(TimelineInner::new(room.to_owned())), None)
90-
}
91-
92-
#[cfg(feature = "experimental-sliding-sync")]
93-
pub(crate) async fn with_events(
94-
room: &room::Common,
95-
prev_token: Option<String>,
96-
events: Vec<SyncTimelineEvent>,
97-
) -> Self {
98-
let mut inner = TimelineInner::new(room.to_owned());
99-
inner.add_initial_events(events).await;
100-
101-
let timeline = Self::from_inner(Arc::new(inner), prev_token);
102-
103-
// The events we're injecting might be encrypted events, but we might
104-
// have received the room key to decrypt them while nobody was listening to the
105-
// `m.room_key` event, let's retry now.
106-
//
107-
// TODO: We could spawn a task here and put this into the background, though it
108-
// might not be worth it depending on the number of events we injected.
109-
// Some measuring needs to be done.
110-
#[cfg(feature = "e2e-encryption")]
111-
timeline.retry_decryption_for_all_events().await;
112-
113-
timeline
114-
}
115-
116-
fn from_inner(inner: Arc<TimelineInner>, prev_token: Option<String>) -> Timeline {
117-
let room = inner.room();
118-
119-
let timeline_event_handle = room.add_event_handler({
120-
let inner = inner.clone();
121-
move |event, encryption_info: Option<EncryptionInfo>| {
122-
let inner = inner.clone();
123-
async move {
124-
inner.handle_live_event(event, encryption_info).await;
125-
}
126-
}
127-
});
128-
129-
// Not using room.add_event_handler here because RoomKey events are
130-
// to-device events that are not received in the context of a room.
131-
#[cfg(feature = "e2e-encryption")]
132-
let room_key_handle = room
133-
.client
134-
.add_event_handler(handle_room_key_event(inner.clone(), room.room_id().to_owned()));
135-
#[cfg(feature = "e2e-encryption")]
136-
let forwarded_room_key_handle = room.client.add_event_handler(
137-
handle_forwarded_room_key_event(inner.clone(), room.room_id().to_owned()),
138-
);
139-
140-
let event_handler_handles = vec![
141-
timeline_event_handle,
142-
#[cfg(feature = "e2e-encryption")]
143-
room_key_handle,
144-
#[cfg(feature = "e2e-encryption")]
145-
forwarded_room_key_handle,
146-
];
147-
148-
Timeline {
149-
inner,
150-
start_token: Mutex::new(prev_token),
151-
_end_token: Mutex::new(None),
152-
event_handler_handles,
153-
}
83+
pub(crate) fn builder(room: &room::Common) -> TimelineBuilder {
84+
TimelineBuilder::new(room)
15485
}
15586

15687
fn room(&self) -> &room::Common {
15788
self.inner.room()
15889
}
15990

160-
/// Enable tracking of the fully-read marker on this `Timeline`.
161-
pub async fn with_fully_read_tracking(mut self) -> Self {
162-
match self.room().account_data_static::<FullyReadEventContent>().await {
163-
Ok(Some(fully_read)) => match fully_read.deserialize() {
164-
Ok(fully_read) => {
165-
self.inner.set_fully_read_event(fully_read.content.event_id).await;
166-
}
167-
Err(e) => {
168-
error!("Failed to deserialize fully-read account data: {e}");
169-
}
170-
},
171-
Err(e) => {
172-
error!("Failed to get fully-read account data from the store: {e}");
173-
}
174-
_ => {}
175-
}
176-
177-
let inner = self.inner.clone();
178-
let fully_read_handle = self.room().add_event_handler(move |event| {
179-
let inner = inner.clone();
180-
async move {
181-
inner.handle_fully_read(event).await;
182-
}
183-
});
184-
self.event_handler_handles.push(fully_read_handle);
185-
186-
self
187-
}
188-
18991
/// Clear all timeline items, and reset pagination parameters.
19092
#[cfg(feature = "experimental-sliding-sync")]
19193
pub async fn clear(&self) {
@@ -311,7 +213,7 @@ impl Timeline {
311213
.await;
312214
}
313215

314-
#[cfg(all(feature = "experimental-sliding-sync", feature = "e2e-encryption"))]
216+
#[cfg(feature = "e2e-encryption")]
315217
async fn retry_decryption_for_all_events(&self) {
316218
self.inner
317219
.retry_event_decryption(

crates/matrix-sdk/src/sliding_sync.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ use tracing::{debug, error, instrument, trace, warn};
4949
use url::Url;
5050

5151
#[cfg(feature = "experimental-timeline")]
52-
use crate::room::timeline::{EventTimelineItem, Timeline};
52+
use crate::room::timeline::{EventTimelineItem, Timeline, TimelineBuilder};
5353
use crate::{config::RequestConfig, Client, Result};
5454

5555
/// Internal representation of errors in Sliding Sync
@@ -241,16 +241,17 @@ impl SlidingSyncRoom {
241241
/// `Timeline` of this room
242242
#[cfg(feature = "experimental-timeline")]
243243
pub async fn timeline(&self) -> Option<Timeline> {
244-
Some(self.timeline_no_fully_read_tracking().await?.with_fully_read_tracking().await)
244+
Some(self.timeline_builder()?.track_fully_read().build().await)
245245
}
246246

247-
async fn timeline_no_fully_read_tracking(&self) -> Option<Timeline> {
247+
#[cfg(feature = "experimental-timeline")]
248+
fn timeline_builder(&self) -> Option<TimelineBuilder> {
248249
if let Some(room) = self.client.get_room(&self.room_id) {
249250
let current_timeline = self.timeline.lock_ref().to_vec();
250251
let prev_batch = self.prev_batch.lock_ref().clone();
251-
Some(Timeline::with_events(&room, prev_batch, current_timeline).await)
252+
Some(Timeline::builder(&room).events(prev_batch, current_timeline))
252253
} else if let Some(invited_room) = self.client.get_invited_room(&self.room_id) {
253-
Some(Timeline::with_events(&invited_room, None, vec![]).await)
254+
Some(Timeline::builder(&invited_room).events(None, vec![]))
254255
} else {
255256
error!(
256257
room_id = ?self.room_id,
@@ -266,7 +267,7 @@ impl SlidingSyncRoom {
266267
/// this `SlidingSyncRoom`.
267268
#[cfg(feature = "experimental-timeline")]
268269
pub async fn latest_event(&self) -> Option<EventTimelineItem> {
269-
self.timeline_no_fully_read_tracking().await?.latest_event()
270+
self.timeline_builder()?.build().await.latest_event()
270271
}
271272

272273
/// This rooms name as calculated by the server, if any

0 commit comments

Comments
 (0)