Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reset the timeline when ignore user list changes #2504

Merged
merged 2 commits into from
Sep 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion crates/matrix-sdk-ui/src/timeline/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ impl TimelineBuilder {
let client = room.client();

let start_token = Arc::new(Mutex::new(prev_token));
let end_token = Arc::new(Mutex::new(None));

let mut room_update_rx = room.subscribe_to_updates();
let room_update_join_handle = spawn({
Expand Down Expand Up @@ -220,6 +221,26 @@ impl TimelineBuilder {
.instrument(info_span!("room_update_handler", room_id = ?room.room_id()))
});

let mut ignore_user_list_stream = client.subscribe_to_ignore_user_list_changes();
let ignore_user_list_update_join_handle = spawn({
let inner = inner.clone();
let start_token = start_token.clone();
let end_token = end_token.clone();
async move {
while ignore_user_list_stream.next().await.is_some() {
// Same as `Timeline::reset`, but Timeline is s not clonable
// and we need to avoid circular references
let mut start_lock = start_token.lock().await;
let mut end_lock = end_token.lock().await;

*start_lock = None;
*end_lock = None;

inner.clear().await;
}
}
});

// Not using room.add_event_handler here because RoomKey events are
// to-device events that are not received in the context of a room.

Expand Down Expand Up @@ -248,12 +269,13 @@ impl TimelineBuilder {
start_token,
start_token_condvar: Default::default(),
back_pagination_status: SharedObservable::new(BackPaginationStatus::Idle),
_end_token: Mutex::new(None),
_end_token: end_token,
msg_sender,
drop_handle: Arc::new(TimelineDropHandle {
client,
event_handler_handles: handles,
room_update_join_handle,
ignore_user_list_update_join_handle,
}),
};

Expand Down
4 changes: 3 additions & 1 deletion crates/matrix-sdk-ui/src/timeline/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ pub struct Timeline {
/// Observable for whether a pagination is currently running
back_pagination_status: SharedObservable<BackPaginationStatus>,

_end_token: Mutex<Option<String>>,
_end_token: Arc<Mutex<Option<String>>>,
msg_sender: Sender<LocalMessage>,
drop_handle: Arc<TimelineDropHandle>,
}
Expand Down Expand Up @@ -673,6 +673,7 @@ struct TimelineDropHandle {
client: Client,
event_handler_handles: Vec<EventHandlerHandle>,
room_update_join_handle: JoinHandle<()>,
ignore_user_list_update_join_handle: JoinHandle<()>,
}

impl Drop for TimelineDropHandle {
Expand All @@ -681,6 +682,7 @@ impl Drop for TimelineDropHandle {
self.client.remove_event_handler(handle);
}
self.room_update_join_handle.abort();
self.ignore_user_list_update_join_handle.abort();
}
}

Expand Down
154 changes: 151 additions & 3 deletions crates/matrix-sdk-ui/tests/integration/timeline/subscribe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,16 @@ use std::time::Duration;

use assert_matches::assert_matches;
use eyeball_im::VectorDiff;
use futures_util::StreamExt;
use futures_util::{pin_mut, StreamExt};
use matrix_sdk::config::SyncSettings;
use matrix_sdk_test::{async_test, JoinedRoomBuilder, SyncResponseBuilder, TimelineTestEvent};
use matrix_sdk_ui::timeline::{RoomExt, TimelineItemContent};
use matrix_sdk_test::{
async_test, GlobalAccountDataTestEvent, JoinedRoomBuilder, SyncResponseBuilder,
TimelineTestEvent,
};
use matrix_sdk_ui::timeline::{RoomExt, TimelineItemContent, VirtualTimelineItem};
use ruma::{event_id, events::room::message::MessageType, room_id};
use serde_json::json;
use stream_assert::{assert_next_matches, assert_pending};

use crate::{logged_in_client, mock_sync};

Expand Down Expand Up @@ -172,3 +176,147 @@ async fn event_filter() {
assert_eq!(text.body, "hi");
assert!(msg.is_edited());
}

#[async_test]
async fn timeline_is_reset_when_a_user_is_ignored_or_unignored() {
let room_id = room_id!("!a98sd12bjh:example.org");
let (client, server) = logged_in_client().await;
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));

let mut ev_builder = SyncResponseBuilder::new();
ev_builder.add_joined_room(JoinedRoomBuilder::new(room_id));

mock_sync(&server, ev_builder.build_json_sync_response(), None).await;
let _response = client.sync_once(sync_settings.clone()).await.unwrap();
server.reset().await;

let room = client.get_room(room_id).unwrap();
let timeline = room.timeline_builder().build().await;
let (_, timeline_stream) = timeline.subscribe().await;
pin_mut!(timeline_stream);

let alice = "@alice:example.org";
let bob = "@bob:example.org";

let first_event_id = event_id!("$YTQwYl2pl1");
let second_event_id = event_id!("$YTQwYl2pl2");
let third_event_id = event_id!("$YTQwYl2pl3");

ev_builder.add_joined_room(
JoinedRoomBuilder::new(room_id)
.add_timeline_event(TimelineTestEvent::Custom(json!({
"content": {
"body": "hello",
"msgtype": "m.text",
},
"event_id": first_event_id,
"origin_server_ts": 152037280,
"sender": alice,
"type": "m.room.message",
})))
.add_timeline_event(TimelineTestEvent::Custom(json!({
"content": {
"body": "hello",
"msgtype": "m.text",
},
"event_id": second_event_id,
"origin_server_ts": 152037281,
"sender": bob,
"type": "m.room.message",
})))
.add_timeline_event(TimelineTestEvent::Custom(json!({
"content": {
"body": "hello",
"msgtype": "m.text",
},
"event_id": third_event_id,
"origin_server_ts": 152037282,
"sender": alice,
"type": "m.room.message",
}))),
);

mock_sync(&server, ev_builder.build_json_sync_response(), None).await;
let _response = client.sync_once(sync_settings.clone()).await.unwrap();
server.reset().await;

assert_next_matches!(timeline_stream, VectorDiff::PushBack { value } => {
assert_matches!(value.as_virtual(), Some(VirtualTimelineItem::DayDivider(_)));
});
assert_next_matches!(timeline_stream, VectorDiff::PushBack { value } => {
assert_eq!(value.as_event().unwrap().event_id(), Some(first_event_id));
});
assert_next_matches!(timeline_stream, VectorDiff::PushBack { value } => {
assert_eq!(value.as_event().unwrap().event_id(), Some(second_event_id));
});
assert_next_matches!(timeline_stream, VectorDiff::Set { index: 1, value } => {
assert_eq!(value.as_event().unwrap().event_id(), Some(first_event_id));
});
assert_next_matches!(timeline_stream, VectorDiff::PushBack { value } => {
assert_eq!(value.as_event().unwrap().event_id(), Some(third_event_id));
});
assert_pending!(timeline_stream);

let fourth_event_id = event_id!("$YTQwYl2pl4");
let fiveth_event_id = event_id!("$YTQwYl2pl5");

ev_builder.add_global_account_data_event(GlobalAccountDataTestEvent::Custom(json!({
"content": {
"ignored_users": {
bob: {}
}
},
"type": "m.ignored_user_list",
})));

mock_sync(&server, ev_builder.build_json_sync_response(), None).await;
let _response = client.sync_once(sync_settings.clone()).await.unwrap();
server.reset().await;

// The timeline has been emptied.
assert_next_matches!(timeline_stream, VectorDiff::Clear);
assert_pending!(timeline_stream);

ev_builder.add_joined_room(
JoinedRoomBuilder::new(room_id)
.add_timeline_event(TimelineTestEvent::Custom(json!({
"content": {
"body": "hello",
"msgtype": "m.text",
},
"event_id": fourth_event_id,
"origin_server_ts": 152037283,
"sender": alice,
"type": "m.room.message",
})))
.add_timeline_event(TimelineTestEvent::Custom(json!({
"content": {
"body": "hello",
"msgtype": "m.text",
},
"event_id": fiveth_event_id,
"origin_server_ts": 152037284,
"sender": alice,
"type": "m.room.message",
}))),
);

mock_sync(&server, ev_builder.build_json_sync_response(), None).await;
let _response = client.sync_once(sync_settings.clone()).await.unwrap();
server.reset().await;

// Timeline receives events as before.
assert_next_matches!(timeline_stream, VectorDiff::PushBack { value } => {
assert_matches!(value.as_virtual(), Some(VirtualTimelineItem::DayDivider(_)));
});
assert_next_matches!(timeline_stream, VectorDiff::PushBack { value } => {
assert_eq!(value.as_event().unwrap().event_id(), Some(fourth_event_id));
});
assert_next_matches!(timeline_stream, VectorDiff::Set { index: 1, value } => {
assert_eq!(value.as_event().unwrap().event_id(), Some(fourth_event_id));
});
assert_next_matches!(timeline_stream, VectorDiff::PushBack { value } => {
assert_eq!(value.as_event().unwrap().event_id(), Some(fiveth_event_id));
});
assert_pending!(timeline_stream);
}