Skip to content

Commit

Permalink
sdk-ui: add TimelineFocus::PinnedEvents and `TimelineFocusData::Pin…
Browse files Browse the repository at this point in the history
…nedEvents` to load the live pinned events for a room.

 Add `PinnedEventsLoader` to encapsulate this logic, being able to provide a max number of events to load and how many can be loaded at the same time. Also implement an event cache for it.

 Add `PinnedEventsRoom` trait to use it in the same way as `PaginableRoom`, only for pinned events.
  • Loading branch information
jmartinesp committed Aug 2, 2024
1 parent 643c9a0 commit 382c573
Show file tree
Hide file tree
Showing 9 changed files with 424 additions and 47 deletions.
51 changes: 44 additions & 7 deletions crates/matrix-sdk-ui/src/timeline/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,12 +158,13 @@ impl TimelineBuilder {
let (_, mut event_subscriber) = room_event_cache.subscribe().await?;

let is_live = matches!(focus, TimelineFocus::Live);
let is_pinned_events = matches!(focus, TimelineFocus::PinnedEvents { .. });
let is_room_encrypted =
room.is_encrypted().await.map_err(|_| Error::UnknownEncryptionState)?;

let inner = TimelineInner::new(
room,
focus,
focus.clone(),
internal_id_prefix,
unable_to_decrypt_hook,
is_room_encrypted,
Expand All @@ -175,6 +176,27 @@ impl TimelineBuilder {
let room = inner.room();
let client = room.client();

let mut pinned_event_ids_stream = room.pinned_event_ids_stream();
let pinned_events_join_handle = if is_pinned_events {
Some(spawn({
let inner = inner.clone();
async move {
while let Some(_) = pinned_event_ids_stream.next().await {
if let Ok(events) = inner.pinned_events_load_events().await {
inner
.replace_with_initial_remote_events(
events,
RemoteEventOrigin::Pagination,
)
.await;
}
}
}
}))
} else {
None
};

let room_update_join_handle = spawn({
let room_event_cache = room_event_cache.clone();
let inner = inner.clone();
Expand All @@ -183,6 +205,8 @@ impl TimelineBuilder {
info_span!(parent: Span::none(), "room_update_handler", room_id = ?room.room_id());
span.follows_from(Span::current());

let focus = Arc::new(focus);

async move {
trace!("Spawned the event subscriber task.");

Expand Down Expand Up @@ -239,13 +263,25 @@ impl TimelineBuilder {
RoomEventCacheUpdate::AddTimelineEvents { events, origin } => {
trace!("Received new timeline events.");

inner.add_events_at(
events,
TimelineEnd::Back,
match origin {
EventsOrigin::Sync => RemoteEventOrigin::Sync,
// Special case for pinned events: when we receive new events what we'll do is
// updated the cache for those events that are pinned and reload the
// list.
match &*focus.clone() {
TimelineFocus::PinnedEvents { .. } => {
if let Ok(events) = inner.pinned_events_load_events().await {
inner.replace_with_initial_remote_events(events, RemoteEventOrigin::Sync).await;
}
}
_ => {
inner.add_events_at(
events,
TimelineEnd::Back,
match origin {
EventsOrigin::Sync => RemoteEventOrigin::Sync,
}
).await;
}
).await;
}
}

RoomEventCacheUpdate::AddEphemeralEvents { events } => {
Expand Down Expand Up @@ -363,6 +399,7 @@ impl TimelineBuilder {
client,
event_handler_handles: handles,
room_update_join_handle,
pinned_events_join_handle,
room_key_from_backups_join_handle,
local_echo_listener_handle,
_event_cache_drop_handle: event_cache_drop,
Expand Down
30 changes: 23 additions & 7 deletions crates/matrix-sdk-ui/src/timeline/event_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ use super::{
};
use crate::{
events::SyncTimelineEventWithoutContent,
timeline::{event_item::ReactionInfo, reactions::PendingReaction},
timeline::{event_item::ReactionInfo, reactions::PendingReaction, traits::RoomDataProvider},
DEFAULT_SANITIZER_MODE,
};

Expand Down Expand Up @@ -266,20 +266,28 @@ pub(super) struct TimelineEventHandler<'a, 'o> {
meta: &'a mut TimelineInnerMetadata,
ctx: TimelineEventContext,
result: HandleEventResult,
is_live_timeline: bool,
live_timeline_updates_type: LiveTimelineUpdatesAllowed,
}

/// Types of live updates expected in this timeline.
#[derive(Debug, Clone)]
pub enum LiveTimelineUpdatesAllowed {
All,
PinnedEvents,
None,
}

impl<'a, 'o> TimelineEventHandler<'a, 'o> {
pub(super) fn new(
state: &'a mut TimelineInnerStateTransaction<'o>,
ctx: TimelineEventContext,
) -> Self {
let TimelineInnerStateTransaction { items, meta, is_live_timeline, .. } = state;
let TimelineInnerStateTransaction { items, meta, live_timeline_updates_type, .. } = state;
Self {
items,
meta,
ctx,
is_live_timeline: *is_live_timeline,
live_timeline_updates_type: live_timeline_updates_type.clone(),
result: HandleEventResult::default(),
}
}
Expand All @@ -291,11 +299,12 @@ impl<'a, 'o> TimelineEventHandler<'a, 'o> {
/// `raw_event` is only needed to determine the cause of any UTDs,
/// so if we know this is not a UTD it can be None.
#[instrument(skip_all, fields(txn_id, event_id, position))]
pub(super) async fn handle_event(
pub(super) async fn handle_event<P: RoomDataProvider>(
mut self,
day_divider_adjuster: &mut DayDividerAdjuster,
event_kind: TimelineEventKind,
raw_event: Option<&Raw<AnySyncTimelineEvent>>,
room_data_provider: &P,
) -> HandleEventResult {
let span = tracing::Span::current();

Expand All @@ -308,7 +317,7 @@ impl<'a, 'o> TimelineEventHandler<'a, 'o> {

// Only add new timeline items if we're in the live mode, i.e. not in the
// event-focused mode.
self.is_live_timeline
matches!(self.live_timeline_updates_type, LiveTimelineUpdatesAllowed::All)
}

Flow::Remote { event_id, txn_id, position, should_add, .. } => {
Expand Down Expand Up @@ -337,7 +346,14 @@ impl<'a, 'o> TimelineEventHandler<'a, 'o> {
// If the event comes the sync (or is unknown), consider adding it only if
// the timeline is in live mode; we don't want to display arbitrary sync
// events in an event-focused timeline.
self.is_live_timeline && *should_add
let can_add_to_live = match self.live_timeline_updates_type {
LiveTimelineUpdatesAllowed::PinnedEvents => {
room_data_provider.room_is_pinned_event_id(event_id)
}
LiveTimelineUpdatesAllowed::All => true,
LiveTimelineUpdatesAllowed::None => false,
};
can_add_to_live && *should_add
}
RemoteEventOrigin::Pagination | RemoteEventOrigin::Cache => {
// Otherwise, forward the previous decision to add it.
Expand Down
88 changes: 78 additions & 10 deletions crates/matrix-sdk-ui/src/timeline/inner/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ use tracing::{debug, error, field::debug, info, instrument, trace, warn};
#[cfg(feature = "e2e-encryption")]
use tracing::{field, info_span, Instrument as _};

pub(super) use self::state::{
EventMeta, FullEventMeta, TimelineEnd, TimelineInnerMetadata, TimelineInnerState,
TimelineInnerStateTransaction,
};
#[cfg(feature = "e2e-encryption")]
use super::traits::Decryptor;
use super::{
Expand All @@ -65,17 +69,17 @@ use super::{
TimelineItemContent, TimelineItemKind,
};
use crate::{
timeline::{day_dividers::DayDividerAdjuster, TimelineEventFilterFn},
timeline::{
day_dividers::DayDividerAdjuster,
event_handler::LiveTimelineUpdatesAllowed,
pinned_events_loader::{PinnedEventsLoader, PinnedEventsLoaderError},
TimelineEventFilterFn,
},
unable_to_decrypt_hook::UtdHookManager,
};

mod state;

pub(super) use self::state::{
EventMeta, FullEventMeta, TimelineEnd, TimelineInnerMetadata, TimelineInnerState,
TimelineInnerStateTransaction,
};

/// Data associated to the current timeline focus.
#[derive(Debug)]
enum TimelineFocusData {
Expand All @@ -92,6 +96,10 @@ enum TimelineFocusData {
/// Number of context events to request for the first request.
num_context_events: u16,
},

PinnedEvents {
loader: PinnedEventsLoader,
},
}

#[derive(Clone, Debug)]
Expand Down Expand Up @@ -230,14 +238,23 @@ impl<P: RoomDataProvider> TimelineInner<P> {
is_room_encrypted: bool,
) -> Self {
let (focus_data, is_live) = match focus {
TimelineFocus::Live => (TimelineFocusData::Live, true),
TimelineFocus::Live => (TimelineFocusData::Live, LiveTimelineUpdatesAllowed::All),
TimelineFocus::Event { target, num_context_events } => {
let paginator = Paginator::new(Box::new(room_data_provider.clone()));
(
TimelineFocusData::Event { paginator, event_id: target, num_context_events },
false,
LiveTimelineUpdatesAllowed::None,
)
}
TimelineFocus::PinnedEvents { max_events_to_load } => (
TimelineFocusData::PinnedEvents {
loader: PinnedEventsLoader::new(
Box::new(room_data_provider.clone()),
max_events_to_load as usize,
),
},
LiveTimelineUpdatesAllowed::PinnedEvents,
),
};

let state = TimelineInnerState::new(
Expand Down Expand Up @@ -300,6 +317,34 @@ impl<P: RoomDataProvider> TimelineInner<P> {

Ok(has_events)
}

TimelineFocusData::PinnedEvents { loader } => {
let loaded_events = loader.load_events().await.map_err(Error::PinnedEventsError)?;

drop(focus_guard);

let has_events = !loaded_events.is_empty();

self.replace_with_initial_remote_events(
loaded_events,
RemoteEventOrigin::Pagination,
)
.await;

Ok(has_events)
}
}
}

pub(crate) async fn pinned_events_load_events(
&self,
) -> Result<Vec<SyncTimelineEvent>, PinnedEventsLoaderError> {
let focus_guard = self.focus.read().await;

if let TimelineFocusData::PinnedEvents { loader } = &*focus_guard {
loader.load_events().await
} else {
Err(PinnedEventsLoaderError::TimelineFocusNotPinnedEvents)
}
}

Expand All @@ -317,6 +362,9 @@ impl<P: RoomDataProvider> TimelineInner<P> {
.paginate_backward(num_events.into())
.await
.map_err(PaginationError::Paginator)?,
TimelineFocusData::PinnedEvents { .. } => {
return Err(PaginationError::NotEventFocusMode)
}
};

self.add_events_at(pagination.events, TimelineEnd::Front, RemoteEventOrigin::Pagination)
Expand All @@ -339,6 +387,9 @@ impl<P: RoomDataProvider> TimelineInner<P> {
.paginate_forward(num_events.into())
.await
.map_err(PaginationError::Paginator)?,
TimelineFocusData::PinnedEvents { .. } => {
return Err(PaginationError::NotEventFocusMode)
}
};

self.add_events_at(pagination.events, TimelineEnd::Back, RemoteEventOrigin::Pagination)
Expand Down Expand Up @@ -452,6 +503,7 @@ impl<P: RoomDataProvider> TimelineInner<P> {
content: event_content.clone(),
relations: Default::default(),
},
&self.room_data_provider,
)
.await;

Expand All @@ -469,7 +521,14 @@ impl<P: RoomDataProvider> TimelineInner<P> {
};

state
.handle_local_event(sender, sender_profile, TransactionId::new(), None, content)
.handle_local_event(
sender,
sender_profile,
TransactionId::new(),
None,
content,
&self.room_data_provider,
)
.await;

// Remember the remote echo to redact on the homeserver.
Expand Down Expand Up @@ -615,7 +674,16 @@ impl<P: RoomDataProvider> TimelineInner<P> {
let profile = self.room_data_provider.profile_from_user_id(&sender).await;

let mut state = self.state.write().await;
state.handle_local_event(sender, profile, txn_id, send_handle, content).await;
state
.handle_local_event(
sender,
profile,
txn_id,
send_handle,
content,
&self.room_data_provider,
)
.await;
}

/// Update the send state of a local event represented by a transaction ID.
Expand Down
Loading

0 comments on commit 382c573

Please sign in to comment.