Skip to content

Commit 5e94610

Browse files
committed
feat(sdk): LatestEventValue represents all kind of suitable latest events.
This patch adds new variants to `LatestEventValue` that represent all the possible suitable latest events. It's heavily inspired by `matrix_sdk_base::latest_event::PossibleLatestEvent`. It uses the new `find_and_map` method to find a suitable `TimelineEvent` and to map it into a `LatestEventValue`. This method is heavily inspired by `matrix_sdk_base::latest_value::is_suitable_for_latest_event`. To be able to provide the `power_levels` to `find_and_map`, a `WeakClient` is required by `LatestEvents::new`. It flows up to a `WeakRoom` in `RoomLatestEvents`, used to create or to update the `LatestEventValue`. A particular care is applied to re-compute the `power_levels` only when necessary and once **per room** (and not per latest event value). Fetching the `power_levels` requires an access to the storage, it's not anodyne.
1 parent 34ccd26 commit 5e94610

File tree

4 files changed

+253
-35
lines changed

4 files changed

+253
-35
lines changed

crates/matrix-sdk/src/client/mod.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2705,7 +2705,11 @@ impl Client {
27052705
self.inner
27062706
.latest_events
27072707
.get_or_init(|| async {
2708-
LatestEvents::new(self.event_cache().clone(), SendQueue::new(self.clone()))
2708+
LatestEvents::new(
2709+
WeakClient::from_client(self),
2710+
self.event_cache().clone(),
2711+
SendQueue::new(self.clone()),
2712+
)
27092713
})
27102714
.await
27112715
}
@@ -2797,7 +2801,7 @@ impl Client {
27972801

27982802
/// A weak reference to the inner client, useful when trying to get a handle
27992803
/// on the owning client.
2800-
#[derive(Clone)]
2804+
#[derive(Clone, Debug)]
28012805
pub(crate) struct WeakClient {
28022806
client: Weak<ClientInner>,
28032807
}

crates/matrix-sdk/src/latest_events/latest_event.rs

Lines changed: 172 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,25 @@
1313
// limitations under the License.
1414

1515
use eyeball::{AsyncLock, SharedObservable, Subscriber};
16-
use ruma::{EventId, OwnedEventId, OwnedRoomId, RoomId};
16+
use matrix_sdk_base::event_cache::Event;
17+
use ruma::{
18+
events::{
19+
call::{invite::SyncCallInviteEvent, notify::SyncCallNotifyEvent},
20+
poll::unstable_start::SyncUnstablePollStartEvent,
21+
relation::RelationType,
22+
room::{
23+
member::{MembershipState, SyncRoomMemberEvent},
24+
message::{MessageType, SyncRoomMessageEvent},
25+
power_levels::RoomPowerLevels,
26+
},
27+
sticker::SyncStickerEvent,
28+
AnySyncMessageLikeEvent, AnySyncStateEvent, AnySyncTimelineEvent,
29+
},
30+
EventId, OwnedEventId, OwnedRoomId, RoomId, UserId,
31+
};
32+
use tracing::warn;
1733

18-
use crate::event_cache::RoomEventCache;
34+
use crate::{event_cache::RoomEventCache, room::WeakRoom};
1935

2036
/// The latest event of a room or a thread.
2137
///
@@ -35,12 +51,13 @@ impl LatestEvent {
3551
room_id: &RoomId,
3652
thread_id: Option<&EventId>,
3753
room_event_cache: &RoomEventCache,
54+
weak_room: &WeakRoom,
3855
) -> Self {
3956
Self {
4057
room_id: room_id.to_owned(),
4158
thread_id: thread_id.map(ToOwned::to_owned),
4259
value: SharedObservable::new_async(
43-
LatestEventValue::new(room_id, thread_id, room_event_cache).await,
60+
LatestEventValue::new(room_id, thread_id, room_event_cache, weak_room).await,
4461
),
4562
}
4663
}
@@ -51,15 +68,24 @@ impl LatestEvent {
5168
}
5269

5370
/// Update the inner latest event value.
54-
pub async fn update(&mut self, room_event_cache: &RoomEventCache) {
55-
let new_value =
56-
LatestEventValue::new(&self.room_id, self.thread_id.as_deref(), room_event_cache).await;
57-
58-
match new_value {
59-
LatestEventValue::None => {
60-
// The new value is `None`. It means no new value has been
61-
// computed. Let's keep the old value.
62-
}
71+
pub async fn update(
72+
&mut self,
73+
room_event_cache: &RoomEventCache,
74+
power_levels: &Option<(&UserId, RoomPowerLevels)>,
75+
) {
76+
let new_value = LatestEventValue::new_with_power_levels(
77+
&self.room_id,
78+
self.thread_id.as_deref(),
79+
room_event_cache,
80+
power_levels,
81+
)
82+
.await;
83+
84+
if let LatestEventValue::None = new_value {
85+
// The new value is `None`. It means no new value has been
86+
// computed. Let's keep the old value.
87+
} else {
88+
self.value.set(new_value).await;
6389
}
6490
}
6591
}
@@ -69,14 +95,146 @@ impl LatestEvent {
6995
pub enum LatestEventValue {
7096
/// No value has been computed yet, or no candidate value was found.
7197
None,
98+
99+
/// A `m.room.message` event.
100+
RoomMessage(SyncRoomMessageEvent),
101+
102+
/// A `m.sticker` event.
103+
Sticker(SyncStickerEvent),
104+
105+
/// An `org.matrix.msc3381.poll.start` event.
106+
Poll(SyncUnstablePollStartEvent),
107+
108+
/// A `m.call.invite` event.
109+
CallInvite(SyncCallInviteEvent),
110+
111+
/// A `m.call.notify` event.
112+
CallNotify(SyncCallNotifyEvent),
113+
114+
/// A `m.room.member` event, more precisely a knock membership change that
115+
/// can be handled by the current user.
116+
KnockedStateEvent(SyncRoomMemberEvent),
72117
}
73118

74119
impl LatestEventValue {
75120
async fn new(
121+
room_id: &RoomId,
122+
thread_id: Option<&EventId>,
123+
room_event_cache: &RoomEventCache,
124+
weak_room: &WeakRoom,
125+
) -> Self {
126+
// Get the power levels of the user for the current room if the `WeakRoom` is
127+
// still valid.
128+
let room = weak_room.get();
129+
let power_levels = match &room {
130+
Some(room) => {
131+
let power_levels = room.power_levels().await.ok();
132+
133+
Some(room.own_user_id()).zip(power_levels)
134+
}
135+
136+
None => None,
137+
};
138+
139+
Self::new_with_power_levels(room_id, thread_id, room_event_cache, &power_levels).await
140+
}
141+
142+
async fn new_with_power_levels(
76143
_room_id: &RoomId,
77144
_thread_id: Option<&EventId>,
78-
_room_event_cache: &RoomEventCache,
145+
room_event_cache: &RoomEventCache,
146+
power_levels: &Option<(&UserId, RoomPowerLevels)>,
79147
) -> Self {
80-
LatestEventValue::None
148+
room_event_cache
149+
.rfind_map_event_in_memory_by(|event| find_and_map(event, power_levels))
150+
.await
151+
.unwrap_or(LatestEventValue::None)
152+
}
153+
}
154+
155+
pub fn find_and_map(
156+
event: &Event,
157+
power_levels: &Option<(&UserId, RoomPowerLevels)>,
158+
) -> Option<LatestEventValue> {
159+
// Cast the event into an `AnySyncTimelineEvent`. If deserializing fails, we
160+
// ignore the event.
161+
let Some(event) = event.raw().deserialize().ok() else {
162+
warn!(?event, "Failed to deserialize the event when looking for a suitable latest event");
163+
164+
return None;
165+
};
166+
167+
match event {
168+
AnySyncTimelineEvent::MessageLike(message_like_event) => match message_like_event {
169+
AnySyncMessageLikeEvent::RoomMessage(message) => {
170+
if let Some(original_message) = message.as_original() {
171+
// Don't show incoming verification requests.
172+
if let MessageType::VerificationRequest(_) = original_message.content.msgtype {
173+
return None;
174+
}
175+
176+
// Check if this is a replacement for another message. If it is, ignore it.
177+
let is_replacement =
178+
original_message.content.relates_to.as_ref().is_some_and(|relates_to| {
179+
if let Some(relation_type) = relates_to.rel_type() {
180+
relation_type == RelationType::Replacement
181+
} else {
182+
false
183+
}
184+
});
185+
186+
if is_replacement {
187+
None
188+
} else {
189+
Some(LatestEventValue::RoomMessage(message))
190+
}
191+
} else {
192+
Some(LatestEventValue::RoomMessage(message))
193+
}
194+
}
195+
196+
AnySyncMessageLikeEvent::UnstablePollStart(poll) => Some(LatestEventValue::Poll(poll)),
197+
198+
AnySyncMessageLikeEvent::CallInvite(invite) => {
199+
Some(LatestEventValue::CallInvite(invite))
200+
}
201+
202+
AnySyncMessageLikeEvent::CallNotify(notify) => {
203+
Some(LatestEventValue::CallNotify(notify))
204+
}
205+
206+
AnySyncMessageLikeEvent::Sticker(sticker) => Some(LatestEventValue::Sticker(sticker)),
207+
208+
// Encrypted events are not suitable.
209+
AnySyncMessageLikeEvent::RoomEncrypted(_) => None,
210+
211+
// Everything else is considered not suitable.
212+
_ => None,
213+
},
214+
215+
// We don't currently support most state events
216+
AnySyncTimelineEvent::State(state) => {
217+
// But we make an exception for knocked state events *if* the current user
218+
// can either accept or decline them.
219+
if let AnySyncStateEvent::RoomMember(member) = state {
220+
if matches!(member.membership(), MembershipState::Knock) {
221+
let can_accept_or_decline_knocks = match power_levels {
222+
Some((own_user_id, room_power_levels)) => {
223+
room_power_levels.user_can_invite(own_user_id)
224+
|| room_power_levels.user_can_kick(own_user_id)
225+
}
226+
_ => false,
227+
};
228+
229+
// The current user can act on the knock changes, so they should be
230+
// displayed
231+
if can_accept_or_decline_knocks {
232+
return Some(LatestEventValue::KnockedStateEvent(member));
233+
}
234+
}
235+
}
236+
237+
None
238+
}
81239
}
82240
}

0 commit comments

Comments
 (0)