From 991c9ad610a6ca06c9945f43e42d856a9ff9988b Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Wed, 22 Jan 2025 09:59:10 +0200 Subject: [PATCH 01/55] chore(ci): simplify formatting checks by using xtask instead --- .github/workflows/ci.yml | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0e94d0757..f3539fe13 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -286,24 +286,6 @@ jobs: run: | target/debug/xtask ci wasm-pack ${{ matrix.cmd }} - formatting: - name: Check Formatting - runs-on: ubuntu-latest - - steps: - - name: Checkout the repo - uses: actions/checkout@v4 - - - name: Install Rust - uses: dtolnay/rust-toolchain@master - with: - toolchain: nightly-2024-11-26 - components: rustfmt - - - name: Cargo fmt - run: | - cargo fmt -- --check - typos: name: Spell Check with Typos runs-on: ubuntu-latest @@ -315,8 +297,8 @@ jobs: - name: Check the spelling of the files in our repo uses: crate-ci/typos@v1.29.4 - clippy: - name: Run clippy + lint: + name: Lint needs: xtask runs-on: ubuntu-latest @@ -333,7 +315,7 @@ jobs: uses: dtolnay/rust-toolchain@master with: toolchain: nightly-2024-11-26 - components: clippy + components: clippy, rustfmt - name: Load cache uses: Swatinem/rust-cache@v2 @@ -347,6 +329,10 @@ jobs: key: "${{ needs.xtask.outputs.cachekey-linux }}" fail-on-cache-miss: true + - name: Check Formatting + run: | + target/debug/xtask ci style + - name: Clippy run: | target/debug/xtask ci clippy From 4684cfb780c44bdd365628a7a2b0d5af01fe91b8 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 22 Jan 2025 09:38:10 +0100 Subject: [PATCH 02/55] chore: Replace `Timeline::subscribe` by `Timeline::subscribe_batched`. This patch changes all calls to `Timeline::subscribe` to replace them by `Timeline::subscribe_batched`. Most of them are in tests. It's the first step of a plan to remove `Timeline::subscribe`. The rest of the patch updates all the tests to use `Timeline::subscribe_batched`. --- benchmarks/benches/room_bench.rs | 2 +- .../tests/integration/room_list_service.rs | 4 +- .../tests/integration/timeline/echo.rs | 67 +++-- .../tests/integration/timeline/edit.rs | 150 +++++++---- .../tests/integration/timeline/focus_event.rs | 44 ++-- .../tests/integration/timeline/mod.rs | 157 ++++++----- .../tests/integration/timeline/pagination.rs | 74 +++--- .../integration/timeline/pinned_event.rs | 246 ++++++++++-------- .../tests/integration/timeline/queue.rs | 87 ++++--- .../tests/integration/timeline/reactions.rs | 167 +++++++----- .../integration/timeline/read_receipts.rs | 48 ++-- .../tests/integration/timeline/replies.rs | 38 ++- .../integration/timeline/sliding_sync.rs | 71 ++--- .../tests/integration/timeline/subscribe.rs | 170 ++++++------ examples/timeline/src/main.rs | 6 +- labs/multiverse/src/main.rs | 9 +- .../src/tests/sliding_sync/room.rs | 42 +-- .../src/tests/timeline.rs | 47 ++-- 18 files changed, 836 insertions(+), 593 deletions(-) diff --git a/benchmarks/benches/room_bench.rs b/benchmarks/benches/room_bench.rs index e8ac47ab0..577bbd762 100644 --- a/benchmarks/benches/room_bench.rs +++ b/benchmarks/benches/room_bench.rs @@ -198,7 +198,7 @@ pub fn load_pinned_events_benchmark(c: &mut Criterion) { .await .expect("Could not create timeline"); - let (items, _) = timeline.subscribe().await; + let (items, _) = timeline.subscribe_batched().await; assert_eq!(items.len(), PINNED_EVENTS_COUNT + 1); timeline.clear().await; }); diff --git a/crates/matrix-sdk-ui/tests/integration/room_list_service.rs b/crates/matrix-sdk-ui/tests/integration/room_list_service.rs index 342ba7c9e..587e5ca77 100644 --- a/crates/matrix-sdk-ui/tests/integration/room_list_service.rs +++ b/crates/matrix-sdk-ui/tests/integration/room_list_service.rs @@ -2430,7 +2430,7 @@ async fn test_room_timeline() -> Result<(), Error> { room.init_timeline_with_builder(room.default_room_timeline_builder().await.unwrap()).await?; let timeline = room.timeline().unwrap(); - let (previous_timeline_items, mut timeline_items_stream) = timeline.subscribe().await; + let (previous_timeline_items, mut timeline_items_stream) = timeline.subscribe_batched().await; sync_then_assert_request_and_fake_response! { [server, room_list, sync] @@ -2493,7 +2493,7 @@ async fn test_room_empty_timeline() { // The room wasn't synced, but it will be available let room = room_list.room(&room_id).unwrap(); let timeline = room.default_room_timeline_builder().await.unwrap().build().await.unwrap(); - let (prev_items, _) = timeline.subscribe().await; + let (prev_items, _) = timeline.subscribe_batched().await; // However, since the room wasn't synced its timeline won't have any initial // items diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/echo.rs b/crates/matrix-sdk-ui/tests/integration/timeline/echo.rs index 2d715963a..b1fd848b9 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/echo.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/echo.rs @@ -19,8 +19,8 @@ use assert_matches2::assert_let; use eyeball_im::VectorDiff; use futures_util::StreamExt; use matrix_sdk::{ - assert_next_matches_with_timeout, config::SyncSettings, executor::spawn, - ruma::MilliSecondsSinceUnixEpoch, test_utils::logged_in_client_with_server, + config::SyncSettings, executor::spawn, ruma::MilliSecondsSinceUnixEpoch, + test_utils::logged_in_client_with_server, }; use matrix_sdk_test::{ async_test, event_factory::EventFactory, mocks::mock_encryption_state, JoinedRoomBuilder, @@ -33,7 +33,7 @@ use ruma::{ room_id, uint, user_id, }; use serde_json::json; -use stream_assert::assert_next_matches; +use stream_assert::{assert_next_matches, assert_pending}; use tokio::task::yield_now; use wiremock::{ matchers::{header, method, path_regex}, @@ -65,7 +65,7 @@ async fn test_echo() { .await .unwrap(), ); - let (_, mut timeline_stream) = timeline.subscribe().await; + let (_, mut timeline_stream) = timeline.subscribe_batched().await; let event_id = event_id!("$ev"); @@ -83,7 +83,10 @@ async fn test_echo() { timeline.send(RoomMessageEventContent::text_plain("Hello, World!").into()).await }); - assert_let!(Some(VectorDiff::PushBack { value: local_echo }) = timeline_stream.next().await); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 2); + + assert_let!(VectorDiff::PushBack { value: local_echo } = &timeline_updates[0]); let item = local_echo.as_event().unwrap(); assert_matches!(item.send_state(), Some(EventSendState::NotSentYet)); assert_let!(TimelineItemContent::Message(msg) = item.content()); @@ -92,15 +95,16 @@ async fn test_echo() { assert!(item.event_id().is_none()); let txn_id = item.transaction_id().unwrap(); - assert_let!(Some(VectorDiff::PushFront { value: date_divider }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushFront { value: date_divider } = &timeline_updates[1]); assert!(date_divider.is_date_divider()); // Wait for the sending to finish and assert everything was successful send_hdl.await.unwrap().unwrap(); - assert_let!( - Some(VectorDiff::Set { index: 1, value: sent_confirmation }) = timeline_stream.next().await - ); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 1); + + assert_let!(VectorDiff::Set { index: 1, value: sent_confirmation } = &timeline_updates[0]); let item = sent_confirmation.as_event().unwrap(); assert_matches!(item.send_state(), Some(EventSendState::Sent { .. })); assert_eq!(item.event_id(), Some(event_id)); @@ -120,19 +124,24 @@ async fn test_echo() { let _response = client.sync_once(sync_settings.clone()).await.unwrap(); server.reset().await; + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 4); + // Local echo is replaced with the remote echo. - assert_next_matches!(timeline_stream, VectorDiff::Remove { index: 1 }); - let remote_echo = - assert_next_matches!(timeline_stream, VectorDiff::PushFront { value } => value); + assert_let!(VectorDiff::Remove { index: 1 } = &timeline_updates[0]); + + assert_let!(VectorDiff::PushFront { value: remote_echo } = &timeline_updates[1]); let item = remote_echo.as_event().unwrap(); assert!(item.is_own()); assert_eq!(item.timestamp(), MilliSecondsSinceUnixEpoch(uint!(152038280))); // The date divider is also replaced. - let date_divider = - assert_next_matches!(timeline_stream, VectorDiff::PushFront { value } => value); + assert_let!(VectorDiff::PushFront { value: date_divider } = &timeline_updates[2]); assert!(date_divider.is_date_divider()); - assert_next_matches!(timeline_stream, VectorDiff::Remove { index: 2 }); + + assert_let!(VectorDiff::Remove { index: 2 } = &timeline_updates[3]); + + assert_pending!(timeline_stream); } #[async_test] @@ -229,7 +238,7 @@ async fn test_dedup_by_event_id_late() { let room = client.get_room(room_id).unwrap(); let timeline = Arc::new(room.timeline().await.unwrap()); - let (_, mut timeline_stream) = timeline.subscribe().await; + let (_, mut timeline_stream) = timeline.subscribe_batched().await; let event_id = event_id!("$wWgymRfo7ri1uQx0NXO40vLJ"); @@ -251,14 +260,16 @@ async fn test_dedup_by_event_id_late() { timeline.send(RoomMessageEventContent::text_plain("Hello, World!").into()).await.unwrap(); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 2); + // Timeline: [local echo] - let local_echo = - assert_next_matches_with_timeout!(timeline_stream, VectorDiff::PushBack { value } => value); + assert_let!(VectorDiff::PushBack { value: local_echo } = &timeline_updates[0]); let item = local_echo.as_event().unwrap(); assert_matches!(item.send_state(), Some(EventSendState::NotSentYet)); // Timeline: [date-divider, local echo] - let date_divider = assert_next_matches_with_timeout!( timeline_stream, VectorDiff::PushFront { value } => value); + assert_let!(VectorDiff::PushFront { value: date_divider } = &timeline_updates[1]); assert!(date_divider.is_date_divider()); let f = EventFactory::new(); @@ -275,21 +286,29 @@ async fn test_dedup_by_event_id_late() { mock_sync(&server, sync_builder.build_json_sync_response(), None).await; let _response = client.sync_once(sync_settings.clone()).await.unwrap(); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 2); + // Timeline: [remote-echo, date-divider, local echo] - let remote_echo = - assert_next_matches!(timeline_stream, VectorDiff::PushFront { value } => value); + assert_let!(VectorDiff::PushFront { value: remote_echo } = &timeline_updates[0]); let item = remote_echo.as_event().unwrap(); assert_eq!(item.event_id(), Some(event_id)); // Timeline: [date-divider, remote-echo, date-divider, local echo] - let date_divider = assert_next_matches_with_timeout!(timeline_stream, VectorDiff::PushFront { value } => value); + assert_let!(VectorDiff::PushFront { value: date_divider } = &timeline_updates[1]); assert!(date_divider.is_date_divider()); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 2); + // Local echo and its date divider are removed. // Timeline: [date-divider, remote-echo, date-divider] - assert_matches!(timeline_stream.next().await, Some(VectorDiff::Remove { index: 3 })); + assert_let!(VectorDiff::Remove { index: 3 } = &timeline_updates[0]); + // Timeline: [date-divider, remote-echo] - assert_matches!(timeline_stream.next().await, Some(VectorDiff::Remove { index: 2 })); + assert_let!(VectorDiff::Remove { index: 2 } = &timeline_updates[1]); + + assert_pending!(timeline_stream); } #[async_test] diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/edit.rs b/crates/matrix-sdk-ui/tests/integration/timeline/edit.rs index 66619fdac..a3eb68aab 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/edit.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/edit.rs @@ -58,7 +58,7 @@ use ruma::{ OwnedRoomId, }; use serde_json::json; -use stream_assert::assert_next_matches; +use stream_assert::{assert_next_matches, assert_pending}; use tokio::{task::yield_now, time::sleep}; use wiremock::{ matchers::{header, method, path_regex}, @@ -86,7 +86,7 @@ async fn test_edit() { let room = client.get_room(room_id).unwrap(); let timeline = room.timeline().await.unwrap(); - let (_, mut timeline_stream) = timeline.subscribe().await; + let (_, mut timeline_stream) = timeline.subscribe_batched().await; let event_id = event_id!("$msda7m:localhost"); sync_builder.add_joined_room( @@ -98,7 +98,10 @@ async fn test_edit() { let _response = client.sync_once(sync_settings.clone()).await.unwrap(); server.reset().await; - assert_let!(Some(VectorDiff::PushBack { value: first }) = timeline_stream.next().await); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 2); + + assert_let!(VectorDiff::PushBack { value: first } = &timeline_updates[0]); let item = first.as_event().unwrap(); assert_eq!(item.read_receipts().len(), 1, "implicit read receipt"); assert_matches!(item.latest_edit_json(), None); @@ -107,7 +110,7 @@ async fn test_edit() { assert_matches!(msg.in_reply_to(), None); assert!(!msg.is_edited()); - assert_let!(Some(VectorDiff::PushFront { value: date_divider }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushFront { value: date_divider } = &timeline_updates[1]); assert!(date_divider.is_date_divider()); sync_builder.add_joined_room( @@ -124,7 +127,10 @@ async fn test_edit() { let _response = client.sync_once(sync_settings.clone()).await.unwrap(); server.reset().await; - assert_let!(Some(VectorDiff::PushBack { value: second }) = timeline_stream.next().await); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 4); + + assert_let!(VectorDiff::PushBack { value: second } = &timeline_updates[0]); let item = second.as_event().unwrap(); assert!(item.event_id().is_some()); assert!(!item.is_own()); @@ -140,7 +146,7 @@ async fn test_edit() { // No more implicit read receipt in Alice's message, because they edited // something after the second event. - assert_let!(Some(VectorDiff::Set { index: 1, value: item }) = timeline_stream.next().await); + assert_let!(VectorDiff::Set { index: 1, value: item } = &timeline_updates[1]); let item = item.as_event().unwrap(); assert_matches!(item.latest_edit_json(), None); assert_let!(TimelineItemContent::Message(msg) = item.content()); @@ -151,7 +157,7 @@ async fn test_edit() { assert_eq!(item.read_receipts().len(), 0, "no more implicit read receipt"); // ... so Alice's read receipt moves to Bob's message. - assert_let!(Some(VectorDiff::Set { index: 2, value: second }) = timeline_stream.next().await); + assert_let!(VectorDiff::Set { index: 2, value: second } = &timeline_updates[2]); let item = second.as_event().unwrap(); assert!(item.event_id().is_some()); assert!(!item.is_own()); @@ -159,7 +165,7 @@ async fn test_edit() { assert_eq!(item.read_receipts().len(), 2, "should carry alice and bob's read receipts"); // The text changes in Alice's message. - assert_let!(Some(VectorDiff::Set { index: 1, value: edit }) = timeline_stream.next().await); + assert_let!(VectorDiff::Set { index: 1, value: edit } = &timeline_updates[3]); let item = edit.as_event().unwrap(); assert_matches!(item.latest_edit_json(), Some(_)); assert_let!(TimelineItemContent::Message(edited) = item.content()); @@ -181,7 +187,7 @@ async fn test_edit_local_echo() { server.mock_room_state_encryption().plain().mount().await; let timeline = room.timeline().await.unwrap(); - let (_, mut timeline_stream) = timeline.subscribe().await; + let (_, mut timeline_stream) = timeline.subscribe_batched().await; let mounted_send = server.mock_room_send().error_too_large().mock_once().mount_as_scoped().await; @@ -189,19 +195,25 @@ async fn test_edit_local_echo() { // Redacting a local event works. timeline.send(RoomMessageEventContent::text_plain("hello, just you").into()).await.unwrap(); - assert_let!(Some(VectorDiff::PushBack { value: item }) = timeline_stream.next().await); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 2); + + assert_let!(VectorDiff::PushBack { value: item } = &timeline_updates[0]); let internal_id = item.unique_id(); let item = item.as_event().unwrap(); assert_matches!(item.send_state(), Some(EventSendState::NotSentYet)); - assert_let!(Some(VectorDiff::PushFront { value: date_divider }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushFront { value: date_divider } = &timeline_updates[1]); assert!(date_divider.is_date_divider()); // We haven't set a route for sending events, so this will fail. - assert_let!(Some(VectorDiff::Set { index: 1, value: item }) = timeline_stream.next().await); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 1); + + assert_let!(VectorDiff::Set { index: 1, value: item } = &timeline_updates[0]); let item = item.as_event().unwrap(); assert!(item.is_local_echo()); @@ -212,7 +224,7 @@ async fn test_edit_local_echo() { Some(EventSendState::SendingFailed { is_recoverable: false, .. }) ); - assert!(timeline_stream.next().now_or_never().is_none()); + assert_pending!(timeline_stream); // Set up the success response before editing, since edit causes an immediate // retry (the room's send queue is not blocked, since the one event it couldn't @@ -229,8 +241,11 @@ async fn test_edit_local_echo() { .await .unwrap(); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 1); + // Observe local echo being replaced. - assert_let!(Some(VectorDiff::Set { index: 1, value: item }) = timeline_stream.next().await); + assert_let!(VectorDiff::Set { index: 1, value: item } = &timeline_updates[0]); assert_eq!(item.unique_id(), internal_id); @@ -246,8 +261,11 @@ async fn test_edit_local_echo() { // Re-enable the room's queue. timeline.room().send_queue().set_enabled(true); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 1); + // Observe the event being sent, and replacing the local echo. - assert_let!(Some(VectorDiff::Set { index: 1, value: item }) = timeline_stream.next().await); + assert_let!(VectorDiff::Set { index: 1, value: item } = &timeline_updates[0]); let item = item.as_event().unwrap(); assert!(item.is_local_echo()); @@ -256,7 +274,7 @@ async fn test_edit_local_echo() { assert_eq!(edit_message.body(), "hello, world"); // No new updates. - assert!(timeline_stream.next().now_or_never().is_none()); + assert_pending!(timeline_stream); } #[async_test] @@ -740,7 +758,7 @@ async fn test_edit_local_echo_with_unsupported_content() { let room = client.get_room(room_id).unwrap(); let timeline = room.timeline().await.unwrap(); - let (_, mut timeline_stream) = timeline.subscribe().await; + let (_, mut timeline_stream) = timeline.subscribe_batched().await; sync_builder.add_joined_room(JoinedRoomBuilder::new(room_id)); @@ -761,17 +779,22 @@ async fn test_edit_local_echo_with_unsupported_content() { timeline.send(RoomMessageEventContent::text_plain("hello, just you").into()).await.unwrap(); - assert_let!(Some(VectorDiff::PushBack { value: item }) = timeline_stream.next().await); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 2); + + assert_let!(VectorDiff::PushBack { value: item } = &timeline_updates[0]); let item = item.as_event().unwrap(); assert_matches!(item.send_state(), Some(EventSendState::NotSentYet)); - assert_let!(Some(VectorDiff::PushFront { value: date_divider }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushFront { value: date_divider } = &timeline_updates[1]); assert!(date_divider.is_date_divider()); // We haven't set a route for sending events, so this will fail. + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 1); - assert_let!(Some(VectorDiff::Set { index: 1, value: item }) = timeline_stream.next().await); + assert_let!(VectorDiff::Set { index: 1, value: item } = &timeline_updates[0]); let item = item.as_event().unwrap(); assert!(item.is_local_echo()); @@ -782,7 +805,7 @@ async fn test_edit_local_echo_with_unsupported_content() { Some(EventSendState::SendingFailed { is_recoverable: false, .. }) ); - assert!(timeline_stream.next().now_or_never().is_none()); + assert_pending!(timeline_stream); // Set up the success response before editing, since edit causes an immediate // retry (the room's send queue is not blocked, since the one event it couldn't @@ -814,7 +837,10 @@ async fn test_edit_local_echo_with_unsupported_content() { .await .unwrap(); - assert_let!(Some(VectorDiff::PushBack { value: item }) = timeline_stream.next().await); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 1); + + assert_let!(VectorDiff::PushBack { value: item } = &timeline_updates[0]); let item = item.as_event().unwrap(); assert_matches!(item.send_state(), Some(EventSendState::NotSentYet)); @@ -832,6 +858,8 @@ async fn test_edit_local_echo_with_unsupported_content() { // We couldn't edit the local echo, since their content types didn't match assert_matches!(edit_err, Error::EditError(EditError::ContentMismatch { .. })); + + assert_pending!(timeline_stream); } struct PendingEditHelper { @@ -888,7 +916,7 @@ async fn test_pending_edit() { let mut h = PendingEditHelper::new().await; let f = EventFactory::new(); - let (_, mut timeline_stream) = h.timeline.subscribe().await; + let (_, mut timeline_stream) = h.timeline.subscribe_batched().await; // When I receive an edit event for an event I don't know about… let original_event_id = event_id!("$original"); @@ -905,7 +933,7 @@ async fn test_pending_edit() { .await; // Nothing happens. - assert!(timeline_stream.next().now_or_never().is_none()); + assert_pending!(timeline_stream); // But when I receive the original event after a bit… h.handle_sync( @@ -914,8 +942,11 @@ async fn test_pending_edit() { ) .await; + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 2); + // Then I get the edited content immediately. - assert_let!(Some(VectorDiff::PushBack { value }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushBack { value } = &timeline_updates[0]); let event = value.as_event().unwrap(); let latest_edit_json = event.latest_edit_json().expect("we should have an edit json"); @@ -926,12 +957,11 @@ async fn test_pending_edit() { assert_eq!(msg.body(), "[edit]"); // The date divider. - assert_next_matches!(timeline_stream, VectorDiff::PushFront { value } => { - assert!(value.is_date_divider()); - }); + assert_let!(VectorDiff::PushFront { value: date_divider } = &timeline_updates[1]); + assert!(date_divider.is_date_divider()); // And nothing else. - assert!(timeline_stream.next().now_or_never().is_none()); + assert_pending!(timeline_stream); } #[async_test] @@ -939,7 +969,7 @@ async fn test_pending_edit_overrides() { let mut h = PendingEditHelper::new().await; let f = EventFactory::new(); - let (_, mut timeline_stream) = h.timeline.subscribe().await; + let (_, mut timeline_stream) = h.timeline.subscribe_batched().await; // When I receive multiple edit events for an event I don't know about… let original_event_id = event_id!("$original"); @@ -963,7 +993,7 @@ async fn test_pending_edit_overrides() { .await; // Nothing happens. - assert!(timeline_stream.next().now_or_never().is_none()); + assert_pending!(timeline_stream); // And then I receive the original event after a bit… h.handle_sync( @@ -972,19 +1002,21 @@ async fn test_pending_edit_overrides() { ) .await; + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 2); + // Then I get the latest edited content immediately. - assert_let!(Some(VectorDiff::PushBack { value }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushBack { value } = &timeline_updates[0]); let msg = value.as_event().unwrap().content().as_message().unwrap(); assert!(msg.is_edited()); assert_eq!(msg.body(), "bonjour"); // The date divider. - assert_next_matches!(timeline_stream, VectorDiff::PushFront { value } => { - assert!(value.is_date_divider()); - }); + assert_let!(VectorDiff::PushFront { value } = &timeline_updates[1]); + assert!(value.is_date_divider()); // And nothing else. - assert!(timeline_stream.next().now_or_never().is_none()); + assert_pending!(timeline_stream); } #[async_test] @@ -992,7 +1024,7 @@ async fn test_pending_edit_from_backpagination() { let mut h = PendingEditHelper::new().await; let f = EventFactory::new(); - let (_, mut timeline_stream) = h.timeline.subscribe().await; + let (_, mut timeline_stream) = h.timeline.subscribe_batched().await; // When I receive an edit from a back-pagination for an event I don't know // about… @@ -1020,19 +1052,21 @@ async fn test_pending_edit_from_backpagination() { ) .await; + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 2); + // Then I get the latest edited content immediately. - assert_let!(Some(VectorDiff::PushBack { value }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushBack { value } = &timeline_updates[0]); let msg = value.as_event().unwrap().content().as_message().unwrap(); assert!(msg.is_edited()); assert_eq!(msg.body(), "hello"); // The date divider. - assert_next_matches!(timeline_stream, VectorDiff::PushFront { value } => { - assert!(value.is_date_divider()); - }); + assert_let!(VectorDiff::PushFront { value } = &timeline_updates[1]); + assert!(value.is_date_divider()); // And nothing else. - assert!(timeline_stream.next().now_or_never().is_none()); + assert_pending!(timeline_stream); } #[async_test] @@ -1056,7 +1090,7 @@ async fn test_pending_edit_from_backpagination_doesnt_override_pending_edit_from ) .await; - let (_, mut timeline_stream) = h.timeline.subscribe().await; + let (_, mut timeline_stream) = h.timeline.subscribe_batched().await; // And then I receive an edit from a back-pagination for the same event… let edit_event_id2 = event_id!("$edit2"); @@ -1073,7 +1107,7 @@ async fn test_pending_edit_from_backpagination_doesnt_override_pending_edit_from .await; // Nothing happens. - assert_matches!(timeline_stream.next().now_or_never(), None); + assert_pending!(timeline_stream); // And then I receive the original event after a bit… h.handle_sync( @@ -1082,20 +1116,22 @@ async fn test_pending_edit_from_backpagination_doesnt_override_pending_edit_from ) .await; + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 2); + // Then I get the edit from the sync, even if the back-pagination happened // after. - assert_let!(Some(VectorDiff::PushBack { value }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushBack { value } = &timeline_updates[0]); let msg = value.as_event().unwrap().content().as_message().unwrap(); assert!(msg.is_edited()); assert_eq!(msg.body(), "[edit]"); // The date divider. - assert_next_matches!(timeline_stream, VectorDiff::PushFront { value } => { - assert!(value.is_date_divider()); - }); + assert_let!(VectorDiff::PushFront { value } = &timeline_updates[1]); + assert!(value.is_date_divider()); // And nothing else. - assert!(timeline_stream.next().now_or_never().is_none()); + assert_pending!(timeline_stream); } #[async_test] @@ -1103,7 +1139,7 @@ async fn test_pending_poll_edit() { let mut h = PendingEditHelper::new().await; let f = EventFactory::new(); - let (_, mut timeline_stream) = h.timeline.subscribe().await; + let (_, mut timeline_stream) = h.timeline.subscribe_batched().await; // When I receive an edit event for an event I don't know about… let original_event_id = event_id!("$original"); @@ -1131,7 +1167,7 @@ async fn test_pending_poll_edit() { .await; // Nothing happens. - assert!(timeline_stream.next().now_or_never().is_none()); + assert_pending!(timeline_stream); // But when I receive the original event after a bit… let event_content = NewUnstablePollStartEventContent::new(UnstablePollStartContentBlock::new( @@ -1149,8 +1185,11 @@ async fn test_pending_poll_edit() { ) .await; + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 2); + // Then I get the edited content immediately. - assert_let!(Some(VectorDiff::PushBack { value }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushBack { value } = &timeline_updates[0]); let poll = as_variant!(value.as_event().unwrap().content(), TimelineItemContent::Poll).unwrap(); assert!(poll.is_edit()); @@ -1160,12 +1199,11 @@ async fn test_pending_poll_edit() { assert_eq!(results.answers[1].text, "No"); // The date divider. - assert_next_matches!(timeline_stream, VectorDiff::PushFront { value } => { - assert!(value.is_date_divider()); - }); + assert_let!(VectorDiff::PushFront { value } = &timeline_updates[1]); + assert!(value.is_date_divider()); // And nothing else. - assert!(timeline_stream.next().now_or_never().is_none()); + assert_pending!(timeline_stream); } #[async_test] diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/focus_event.rs b/crates/matrix-sdk-ui/tests/integration/timeline/focus_event.rs index ccb208742..f4162d429 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/focus_event.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/focus_event.rs @@ -19,10 +19,7 @@ use std::time::Duration; use assert_matches2::assert_let; use eyeball_im::VectorDiff; use futures_util::StreamExt; -use matrix_sdk::{ - assert_next_matches_with_timeout, config::SyncSettings, - test_utils::logged_in_client_with_server, -}; +use matrix_sdk::{config::SyncSettings, test_utils::logged_in_client_with_server}; use matrix_sdk_test::{ async_test, event_factory::EventFactory, mocks::mock_encryption_state, JoinedRoomBuilder, SyncResponseBuilder, ALICE, BOB, @@ -89,7 +86,7 @@ async fn test_new_focused() { server.reset().await; - let (items, mut timeline_stream) = timeline.subscribe().await; + let (items, mut timeline_stream) = timeline.subscribe_batched().await; assert_eq!(items.len(), 5 + 1); // event items + a date divider assert!(items[0].is_date_divider()); @@ -129,23 +126,27 @@ async fn test_new_focused() { server.reset().await; - assert_let!(Some(VectorDiff::PushFront { value: message }) = timeline_stream.next().await); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 4); + + assert_let!(VectorDiff::PushFront { value: message } = &timeline_updates[0]); assert_eq!( message.as_event().unwrap().content().as_message().unwrap().body(), "And even though I tried, it all fell apart" ); - assert_let!(Some(VectorDiff::PushFront { value: message }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushFront { value: message } = &timeline_updates[1]); assert_eq!( message.as_event().unwrap().content().as_message().unwrap().body(), "I kept everything inside" ); // Date divider post processing. - assert_let!(Some(VectorDiff::PushFront { value: item }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushFront { value: item } = &timeline_updates[2]); assert!(item.is_date_divider()); - assert_let!(Some(VectorDiff::Remove { index }) = timeline_stream.next().await); - assert_eq!(index, 3); + + assert_let!(VectorDiff::Remove { index } = &timeline_updates[3]); + assert_eq!(*index, 3); // Now trigger a forward pagination. mock_messages( @@ -165,13 +166,16 @@ async fn test_new_focused() { server.reset().await; - assert_let!(Some(VectorDiff::PushBack { value: message }) = timeline_stream.next().await); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 2); + + assert_let!(VectorDiff::PushBack { value: message } = &timeline_updates[0]); assert_eq!( message.as_event().unwrap().content().as_message().unwrap().body(), "I had to fall, to lose it all" ); - assert_let!(Some(VectorDiff::PushBack { value: message }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushBack { value: message } = &timeline_updates[1]); assert_eq!( message.as_event().unwrap().content().as_message().unwrap().body(), "But in the end, it doesn't event matter" @@ -225,7 +229,7 @@ async fn test_focused_timeline_reacts() { server.reset().await; - let (items, mut timeline_stream) = timeline.subscribe().await; + let (items, mut timeline_stream) = timeline.subscribe_batched().await; assert_eq!(items.len(), 1 + 1); // event items + a date divider assert!(items[0].is_date_divider()); @@ -250,7 +254,10 @@ async fn test_focused_timeline_reacts() { let _response = client.sync_once(sync_settings.clone()).await.unwrap(); server.reset().await; - let item = assert_next_matches_with_timeout!(timeline_stream, VectorDiff::Set { index: 1, value: item } => item); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 1); + + assert_let!(VectorDiff::Set { index: 1, value: item } = &timeline_updates[0]); let event_item = item.as_event().unwrap(); // Text hasn't changed. @@ -307,7 +314,7 @@ async fn test_focused_timeline_local_echoes() { server.reset().await; - let (items, mut timeline_stream) = timeline.subscribe().await; + let (items, mut timeline_stream) = timeline.subscribe_batched().await; assert_eq!(items.len(), 1 + 1); // event items + a date divider assert!(items[0].is_date_divider()); @@ -322,8 +329,11 @@ async fn test_focused_timeline_local_echoes() { // Add a reaction to the focused event, which will cause a local echo to happen. timeline.toggle_reaction(&event_item.identifier(), "✨").await.unwrap(); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 1); + // We immediately get the local echo for the reaction. - let item = assert_next_matches_with_timeout!(timeline_stream, VectorDiff::Set { index: 1, value: item } => item); + assert_let!(VectorDiff::Set { index: 1, value: item } = &timeline_updates[0]); let event_item = item.as_event().unwrap(); // Text hasn't changed. @@ -383,7 +393,7 @@ async fn test_focused_timeline_doesnt_show_local_echoes() { server.reset().await; - let (items, mut timeline_stream) = timeline.subscribe().await; + let (items, mut timeline_stream) = timeline.subscribe_batched().await; assert_eq!(items.len(), 1 + 1); // event items + a date divider assert!(items[0].is_date_divider()); diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/mod.rs b/crates/matrix-sdk-ui/tests/integration/timeline/mod.rs index a54081b95..83f2bdb2e 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/mod.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/mod.rs @@ -19,7 +19,6 @@ use assert_matches2::assert_let; use eyeball_im::VectorDiff; use futures_util::StreamExt; use matrix_sdk::{ - assert_let_timeout, config::SyncSettings, test_utils::{logged_in_client_with_server, mocks::MatrixMockServer}, }; @@ -40,7 +39,7 @@ use ruma::{ owned_event_id, room_id, user_id, MilliSecondsSinceUnixEpoch, }; use serde_json::json; -use stream_assert::{assert_next_matches, assert_pending}; +use stream_assert::assert_pending; use wiremock::{ matchers::{header, method, path_regex}, Mock, ResponseTemplate, @@ -80,7 +79,7 @@ async fn test_reaction() { let room = client.get_room(room_id).unwrap(); let timeline = room.timeline().await.unwrap(); - let (_, mut timeline_stream) = timeline.subscribe().await; + let (_, mut timeline_stream) = timeline.subscribe_batched().await; sync_builder.add_joined_room( JoinedRoomBuilder::new(room_id) @@ -113,17 +112,18 @@ async fn test_reaction() { let _response = client.sync_once(sync_settings.clone()).await.unwrap(); server.reset().await; + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 4); + // The new message starts with their author's read receipt. - assert_let_timeout!(Some(VectorDiff::PushBack { value: message }) = timeline_stream.next()); + assert_let!(VectorDiff::PushBack { value: message } = &timeline_updates[0]); let event_item = message.as_event().unwrap(); assert_matches!(event_item.content(), TimelineItemContent::Message(_)); assert_eq!(event_item.read_receipts().len(), 1); // The new message is getting the reaction, which implies an implicit read // receipt that's obtained first. - assert_let_timeout!( - Some(VectorDiff::Set { index: 0, value: updated_message }) = timeline_stream.next() - ); + assert_let!(VectorDiff::Set { index: 0, value: updated_message } = &timeline_updates[1]); let event_item = updated_message.as_event().unwrap(); assert_let!(TimelineItemContent::Message(msg) = event_item.content()); assert!(!msg.is_edited()); @@ -131,9 +131,7 @@ async fn test_reaction() { assert_eq!(event_item.reactions().len(), 0); // Then the reaction is taken into account. - assert_let_timeout!( - Some(VectorDiff::Set { index: 0, value: updated_message }) = timeline_stream.next() - ); + assert_let!(VectorDiff::Set { index: 0, value: updated_message } = &timeline_updates[2]); let event_item = updated_message.as_event().unwrap(); assert_let!(TimelineItemContent::Message(msg) = event_item.content()); assert!(!msg.is_edited()); @@ -145,9 +143,7 @@ async fn test_reaction() { assert_eq!(senders.as_slice(), [user_id!("@bob:example.org")]); // The date divider. - assert_let_timeout!( - Some(VectorDiff::PushFront { value: date_divider }) = timeline_stream.next() - ); + assert_let!(VectorDiff::PushFront { value: date_divider } = &timeline_updates[3]); assert!(date_divider.is_date_divider()); sync_builder.add_joined_room(JoinedRoomBuilder::new(room_id).add_timeline_event( @@ -165,13 +161,16 @@ async fn test_reaction() { let _response = client.sync_once(sync_settings.clone()).await.unwrap(); server.reset().await; - assert_let_timeout!( - Some(VectorDiff::Set { index: 1, value: updated_message }) = timeline_stream.next() - ); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 1); + + assert_let!(VectorDiff::Set { index: 1, value: updated_message } = &timeline_updates[0]); let event_item = updated_message.as_event().unwrap(); assert_let!(TimelineItemContent::Message(msg) = event_item.content()); assert!(!msg.is_edited()); assert_eq!(event_item.reactions().len(), 0); + + assert_pending!(timeline_stream); } #[async_test] @@ -191,7 +190,7 @@ async fn test_redacted_message() { let room = client.get_room(room_id).unwrap(); let timeline = room.timeline().await.unwrap(); - let (_, mut timeline_stream) = timeline.subscribe().await; + let (_, mut timeline_stream) = timeline.subscribe_batched().await; sync_builder.add_joined_room( JoinedRoomBuilder::new(room_id) @@ -226,11 +225,16 @@ async fn test_redacted_message() { let _response = client.sync_once(sync_settings.clone()).await.unwrap(); server.reset().await; - assert_let!(Some(VectorDiff::PushBack { value: first }) = timeline_stream.next().await); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 2); + + assert_let!(VectorDiff::PushBack { value: first } = &timeline_updates[0]); assert_matches!(first.as_event().unwrap().content(), TimelineItemContent::RedactedMessage); - assert_let!(Some(VectorDiff::PushFront { value: date_divider }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushFront { value: date_divider } = &timeline_updates[1]); assert!(date_divider.is_date_divider()); + + assert_pending!(timeline_stream); } #[async_test] @@ -244,7 +248,7 @@ async fn test_redact_message() { server.mock_room_state_encryption().plain().mount().await; let timeline = room.timeline().await.unwrap(); - let (_, mut timeline_stream) = timeline.subscribe().await; + let (_, mut timeline_stream) = timeline.subscribe_batched().await; let factory = EventFactory::new(); factory.set_next_ts(MilliSecondsSinceUnixEpoch::now().get().into()); @@ -258,13 +262,16 @@ async fn test_redact_message() { ) .await; - assert_let!(Some(VectorDiff::PushBack { value: first }) = timeline_stream.next().await); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 2); + + assert_let!(VectorDiff::PushBack { value: first } = &timeline_updates[0]); assert_eq!( first.as_event().unwrap().content().as_message().unwrap().body(), "buy my bitcoins bro" ); - assert_let!(Some(VectorDiff::PushFront { value: date_divider }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushFront { value: date_divider } = &timeline_updates[1]); assert!(date_divider.is_date_divider()); // Redacting a remote event works. @@ -278,14 +285,20 @@ async fn test_redact_message() { .await .unwrap(); - assert_let!(Some(VectorDiff::PushBack { value: second }) = timeline_stream.next().await); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 1); + + assert_let!(VectorDiff::PushBack { value: second } = &timeline_updates[0]); let second = second.as_event().unwrap(); assert_matches!(second.send_state(), Some(EventSendState::NotSentYet)); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 1); + // We haven't set a route for sending events, so this will fail. - assert_let!(Some(VectorDiff::Set { index, value: second }) = timeline_stream.next().await); - assert_eq!(index, 2); + assert_let!(VectorDiff::Set { index, value: second } = &timeline_updates[0]); + assert_eq!(*index, 2); let second = second.as_event().unwrap(); assert!(second.is_local_echo()); @@ -294,8 +307,13 @@ async fn test_redact_message() { // Let's redact the local echo. timeline.redact(&second.identifier(), None).await.unwrap(); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 1); + // Observe local echo being removed. - assert_matches!(timeline_stream.next().await, Some(VectorDiff::Remove { index: 2 })); + assert_let!(VectorDiff::Remove { index: 2 } = &timeline_updates[0]); + + assert_pending!(timeline_stream); } #[async_test] @@ -309,7 +327,7 @@ async fn test_redact_local_sent_message() { server.mock_room_state_encryption().plain().mount().await; let timeline = room.timeline().await.unwrap(); - let (_, mut timeline_stream) = timeline.subscribe().await; + let (_, mut timeline_stream) = timeline.subscribe_batched().await; // Mock event sending. server.mock_room_send().ok(event_id!("$wWgymRfo7ri1uQx0NXO40vLJ")).mock_once().mount().await; @@ -320,21 +338,27 @@ async fn test_redact_local_sent_message() { .await .unwrap(); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 2); + // Assert the local event is in the timeline now and is not sent yet. - assert_let_timeout!(Some(VectorDiff::PushBack { value: item }) = timeline_stream.next()); + assert_let!(VectorDiff::PushBack { value: item } = &timeline_updates[0]); let event = item.as_event().unwrap(); assert!(event.is_local_echo()); assert_matches!(event.send_state(), Some(EventSendState::NotSentYet)); // As well as a date divider. - assert_let_timeout!( - Some(VectorDiff::PushFront { value: date_divider }) = timeline_stream.next() - ); + assert_let!(VectorDiff::PushFront { value: date_divider } = &timeline_updates[1]); assert!(date_divider.is_date_divider()); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 1); + // We receive an update in the timeline from the send queue. - assert_let_timeout!(Some(VectorDiff::Set { index, value: item }) = timeline_stream.next()); - assert_eq!(index, 1); + assert_let!(VectorDiff::Set { index, value: item } = &timeline_updates[0]); + assert_eq!(*index, 1); + + assert_pending!(timeline_stream); // Check the event is sent but still considered local. let event = item.as_event().unwrap(); @@ -396,7 +420,7 @@ async fn test_read_marker() { let room = client.get_room(room_id).unwrap(); let timeline = room.timeline().await.unwrap(); - let (_, mut timeline_stream) = timeline.subscribe().await; + let (_, mut timeline_stream) = timeline.subscribe_batched().await; sync_builder.add_joined_room(JoinedRoomBuilder::new(room_id).add_timeline_event( sync_timeline_event!({ @@ -415,10 +439,13 @@ async fn test_read_marker() { let _response = client.sync_once(sync_settings.clone()).await.unwrap(); server.reset().await; - assert_let!(Some(VectorDiff::PushBack { value: message }) = timeline_stream.next().await); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 2); + + assert_let!(VectorDiff::PushBack { value: message } = &timeline_updates[0]); assert_matches!(message.as_event().unwrap().content(), TimelineItemContent::Message(_)); - assert_let!(Some(VectorDiff::PushFront { value: date_divider }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushFront { value: date_divider } = &timeline_updates[1]); assert!(date_divider.is_date_divider()); sync_builder.add_joined_room( @@ -448,13 +475,16 @@ async fn test_read_marker() { let _response = client.sync_once(sync_settings.clone()).await.unwrap(); server.reset().await; - assert_let!(Some(VectorDiff::PushBack { value: message }) = timeline_stream.next().await); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 2); + + assert_let!(VectorDiff::PushBack { value: message } = &timeline_updates[0]); assert_matches!(message.as_event().unwrap().content(), TimelineItemContent::Message(_)); - assert_let!( - Some(VectorDiff::Insert { index: 2, value: marker }) = timeline_stream.next().await - ); + assert_let!(VectorDiff::Insert { index: 2, value: marker } = &timeline_updates[1]); assert_matches!(marker.as_virtual().unwrap(), VirtualTimelineItem::ReadMarker); + + assert_pending!(timeline_stream); } #[async_test] @@ -480,7 +510,7 @@ async fn test_sync_highlighted() { let room = client.get_room(room_id).unwrap(); let timeline = room.timeline().await.unwrap(); - let (_, mut timeline_stream) = timeline.subscribe().await; + let (_, mut timeline_stream) = timeline.subscribe_batched().await; sync_builder.add_joined_room(JoinedRoomBuilder::new(room_id).add_timeline_event( sync_timeline_event!({ @@ -499,12 +529,15 @@ async fn test_sync_highlighted() { let _response = client.sync_once(sync_settings.clone()).await.unwrap(); server.reset().await; - assert_let!(Some(VectorDiff::PushBack { value: first }) = timeline_stream.next().await); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 2); + + assert_let!(VectorDiff::PushBack { value: first } = &timeline_updates[0]); let remote_event = first.as_event().unwrap(); // Own events don't trigger push rules. assert!(!remote_event.is_highlighted()); - assert_let!(Some(VectorDiff::PushFront { value: date_divider }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushFront { value: date_divider } = &timeline_updates[1]); assert!(date_divider.is_date_divider()); sync_builder.add_joined_room(JoinedRoomBuilder::new(room_id).add_timeline_event( @@ -525,10 +558,15 @@ async fn test_sync_highlighted() { let _response = client.sync_once(sync_settings.clone()).await.unwrap(); server.reset().await; - assert_let!(Some(VectorDiff::PushBack { value: second }) = timeline_stream.next().await); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 1); + + assert_let!(VectorDiff::PushBack { value: second } = &timeline_updates[0]); let remote_event = second.as_event().unwrap(); // `m.room.tombstone` should be highlighted by default. assert!(remote_event.is_highlighted()); + + assert_pending!(timeline_stream); } #[async_test] @@ -724,7 +762,7 @@ async fn test_timeline_without_encryption_info() { // Previously this would have panicked. let timeline = room.timeline().await.unwrap(); - let (items, _) = timeline.subscribe().await; + let (items, _) = timeline.subscribe_batched().await; assert_eq!(items.len(), 2); assert!(items[0].as_virtual().is_some()); // No encryption, no shields @@ -756,7 +794,7 @@ async fn test_timeline_without_encryption_can_update() { // encryption changes let timeline = Timeline::builder(&room).build().await.unwrap(); - let (items, mut stream) = timeline.subscribe().await; + let (items, mut stream) = timeline.subscribe_batched().await; assert_eq!(items.len(), 2); assert!(items[0].as_virtual().is_some()); // No encryption, no shields @@ -772,21 +810,24 @@ async fn test_timeline_without_encryption_can_update() { let _response = client.sync_once(sync_settings.clone()).await.unwrap(); server.reset().await; + assert_let!(Some(timeline_updates) = stream.next().await); + assert_eq!(timeline_updates.len(), 3); + // Previous timeline event now has a shield - assert_next_matches!(stream, VectorDiff::Set { index, value } => { - assert_eq!(index, 1); - assert!(value.as_event().unwrap().get_shield(false).is_some()); - }); + assert_let!(VectorDiff::Set { index, value } = &timeline_updates[0]); + assert_eq!(*index, 1); + assert!(value.as_event().unwrap().get_shield(false).is_some()); + // Room encryption event is received - assert_next_matches!(stream, VectorDiff::PushBack { value } => { - assert_let!(TimelineItemContent::OtherState(other_state) = value.as_event().unwrap().content()); - assert_let!(AnyOtherFullStateEventContent::RoomEncryption(_) = other_state.content()); - assert!(value.as_event().unwrap().get_shield(false).is_some()); - }); + assert_let!(VectorDiff::PushBack { value } = &timeline_updates[1]); + assert_let!(TimelineItemContent::OtherState(other_state) = value.as_event().unwrap().content()); + assert_let!(AnyOtherFullStateEventContent::RoomEncryption(_) = other_state.content()); + assert!(value.as_event().unwrap().get_shield(false).is_some()); + // New message event is received and has a shield - assert_next_matches!(stream, VectorDiff::PushBack { value } => { - assert!(value.as_event().unwrap().get_shield(false).is_some()); - }); + assert_let!(VectorDiff::PushBack { value } = &timeline_updates[2]); + assert!(value.as_event().unwrap().get_shield(false).is_some()); + assert_pending!(stream); } diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/pagination.rs b/crates/matrix-sdk-ui/tests/integration/timeline/pagination.rs index deea9e419..f8efef54b 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/pagination.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/pagination.rs @@ -63,7 +63,7 @@ async fn test_back_pagination() { let room = client.get_room(room_id).unwrap(); let timeline = Arc::new(room.timeline().await.unwrap()); - let (_, mut timeline_stream) = timeline.subscribe().await; + let (_, mut timeline_stream) = timeline.subscribe_batched().await; let (_, mut back_pagination_status) = timeline.live_back_pagination_status().await.unwrap(); Mock::given(method("GET")) @@ -84,9 +84,11 @@ async fn test_back_pagination() { }; join(paginate, observe_paginating).await; + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + // `m.room.name` { - assert_let!(Some(VectorDiff::PushBack { value: message }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushBack { value: message } = &timeline_updates[0]); assert_let!(TimelineItemContent::OtherState(state) = message.as_event().unwrap().content()); assert_eq!(state.state_key(), ""); assert_let!( @@ -101,13 +103,13 @@ async fn test_back_pagination() { // `m.room.name` receives an update { - assert_let!(Some(VectorDiff::Set { index, .. }) = timeline_stream.next().await); - assert_eq!(index, 0); + assert_let!(VectorDiff::Set { index, .. } = &timeline_updates[1]); + assert_eq!(*index, 0); } // `m.room.message`: “the world is big” { - assert_let!(Some(VectorDiff::PushBack { value: message }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushBack { value: message } = &timeline_updates[2]); assert_let!(TimelineItemContent::Message(msg) = message.as_event().unwrap().content()); assert_let!(MessageType::Text(text) = msg.msgtype()); assert_eq!(text.body, "the world is big"); @@ -115,7 +117,7 @@ async fn test_back_pagination() { // `m.room.message`: “hello world” { - assert_let!(Some(VectorDiff::PushBack { value: message }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushBack { value: message } = &timeline_updates[3]); assert_let!(TimelineItemContent::Message(msg) = message.as_event().unwrap().content()); assert_let!(MessageType::Text(text) = msg.msgtype()); assert_eq!(text.body, "hello world"); @@ -123,9 +125,7 @@ async fn test_back_pagination() { // Date divider is updated. { - assert_let!( - Some(VectorDiff::PushFront { value: date_divider }) = timeline_stream.next().await - ); + assert_let!(VectorDiff::PushFront { value: date_divider } = &timeline_updates[4]); assert!(date_divider.is_date_divider()); } @@ -178,7 +178,7 @@ async fn test_back_pagination_highlighted() { let room = client.get_room(room_id).unwrap(); let timeline = Arc::new(room.timeline().await.unwrap()); - let (_, mut timeline_stream) = timeline.subscribe().await; + let (_, mut timeline_stream) = timeline.subscribe_batched().await; let response_json = json!({ "chunk": [ @@ -221,9 +221,11 @@ async fn test_back_pagination_highlighted() { timeline.live_paginate_backwards(10).await.unwrap(); server.reset().await; + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + // `m.room.tombstone` { - assert_let!(Some(VectorDiff::PushBack { value: second }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushBack { value: second } = &timeline_updates[0]); let remote_event = second.as_event().unwrap(); // `m.room.tombstone` should be highlighted by default. assert!(remote_event.is_highlighted()); @@ -231,7 +233,7 @@ async fn test_back_pagination_highlighted() { // `m.room.message` { - assert_let!(Some(VectorDiff::PushBack { value: first }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushBack { value: first } = &timeline_updates[1]); let remote_event = first.as_event().unwrap(); // Own events don't trigger push rules. assert!(!remote_event.is_highlighted()); @@ -239,9 +241,7 @@ async fn test_back_pagination_highlighted() { // Date divider { - assert_let!( - Some(VectorDiff::PushFront { value: date_divider }) = timeline_stream.next().await - ); + assert_let!(VectorDiff::PushFront { value: date_divider } = &timeline_updates[2]); assert!(date_divider.is_date_divider()); } @@ -579,7 +579,7 @@ async fn test_empty_chunk() { let room = client.get_room(room_id).unwrap(); let timeline = Arc::new(room.timeline().await.unwrap()); - let (_, mut timeline_stream) = timeline.subscribe().await; + let (_, mut timeline_stream) = timeline.subscribe_batched().await; let (_, mut back_pagination_status) = timeline.live_back_pagination_status().await.unwrap(); // It should try to do another request after the empty chunk. @@ -616,9 +616,11 @@ async fn test_empty_chunk() { }; join(paginate, observe_paginating).await; + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + // `m.room.name` { - assert_let!(Some(VectorDiff::PushBack { value: message }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushBack { value: message } = &timeline_updates[0]); assert_let!(TimelineItemContent::OtherState(state) = message.as_event().unwrap().content()); assert_eq!(state.state_key(), ""); assert_let!( @@ -633,13 +635,13 @@ async fn test_empty_chunk() { // `m.room.name` is updated { - assert_let!(Some(VectorDiff::Set { index, .. }) = timeline_stream.next().await); - assert_eq!(index, 0); + assert_let!(VectorDiff::Set { index, .. } = &timeline_updates[1]); + assert_eq!(*index, 0); } // `m.room.message`: “the world is big” { - assert_let!(Some(VectorDiff::PushBack { value: message }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushBack { value: message } = &timeline_updates[2]); assert_let!(TimelineItemContent::Message(msg) = message.as_event().unwrap().content()); assert_let!(MessageType::Text(text) = msg.msgtype()); assert_eq!(text.body, "the world is big"); @@ -647,7 +649,7 @@ async fn test_empty_chunk() { // `m.room.name`: “hello world” { - assert_let!(Some(VectorDiff::PushBack { value: message }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushBack { value: message } = &timeline_updates[3]); assert_let!(TimelineItemContent::Message(msg) = message.as_event().unwrap().content()); assert_let!(MessageType::Text(text) = msg.msgtype()); assert_eq!(text.body, "hello world"); @@ -655,9 +657,7 @@ async fn test_empty_chunk() { // Date divider { - assert_let!( - Some(VectorDiff::PushFront { value: date_divider }) = timeline_stream.next().await - ); + assert_let!(VectorDiff::PushFront { value: date_divider } = &timeline_updates[4]); assert!(date_divider.is_date_divider()); } @@ -681,7 +681,7 @@ async fn test_until_num_items_with_empty_chunk() { let room = client.get_room(room_id).unwrap(); let timeline = Arc::new(room.timeline().await.unwrap()); - let (_, mut timeline_stream) = timeline.subscribe().await; + let (_, mut timeline_stream) = timeline.subscribe_batched().await; let (_, mut back_pagination_status) = timeline.live_back_pagination_status().await.unwrap(); Mock::given(method("GET")) @@ -726,9 +726,11 @@ async fn test_until_num_items_with_empty_chunk() { }; join(paginate, observe_paginating).await; + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + // `m.room.name` { - assert_let!(Some(VectorDiff::PushBack { value: message }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushBack { value: message } = &timeline_updates[0]); assert_let!(TimelineItemContent::OtherState(state) = message.as_event().unwrap().content()); assert_eq!(state.state_key(), ""); assert_let!( @@ -743,13 +745,13 @@ async fn test_until_num_items_with_empty_chunk() { // `m.room.name` is updated { - assert_let!(Some(VectorDiff::Set { index, .. }) = timeline_stream.next().await); - assert_eq!(index, 0); + assert_let!(VectorDiff::Set { index, .. } = &timeline_updates[1]); + assert_eq!(*index, 0); } // `m.room.message`: “the world is big” { - assert_let!(Some(VectorDiff::PushBack { value: message }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushBack { value: message } = &timeline_updates[2]); assert_let!(TimelineItemContent::Message(msg) = message.as_event().unwrap().content()); assert_let!(MessageType::Text(text) = msg.msgtype()); assert_eq!(text.body, "the world is big"); @@ -757,7 +759,7 @@ async fn test_until_num_items_with_empty_chunk() { // `m.room.name`: “hello world” { - assert_let!(Some(VectorDiff::PushBack { value: message }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushBack { value: message } = &timeline_updates[3]); assert_let!(TimelineItemContent::Message(msg) = message.as_event().unwrap().content()); assert_let!(MessageType::Text(text) = msg.msgtype()); assert_eq!(text.body, "hello world"); @@ -765,20 +767,18 @@ async fn test_until_num_items_with_empty_chunk() { // Date divider { - assert_let!( - Some(VectorDiff::PushFront { value: date_divider }) = timeline_stream.next().await - ); + assert_let!(VectorDiff::PushFront { value: date_divider } = &timeline_updates[4]); assert!(date_divider.is_date_divider()); } timeline.live_paginate_backwards(10).await.unwrap(); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + // `m.room.name`: “hello room then” { - assert_let!( - Some(VectorDiff::Insert { index, value: message }) = timeline_stream.next().await - ); - assert_eq!(index, 1); + assert_let!(VectorDiff::Insert { index, value: message } = &timeline_updates[0]); + assert_eq!(*index, 1); assert_let!(TimelineItemContent::Message(msg) = message.as_event().unwrap().content()); assert_let!(MessageType::Text(text) = msg.msgtype()); assert_eq!(text.body, "hello room then"); diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/pinned_event.rs b/crates/matrix-sdk-ui/tests/integration/timeline/pinned_event.rs index dfbf12b26..943d6d5f5 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/pinned_event.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/pinned_event.rs @@ -1,9 +1,10 @@ use std::{ops::ControlFlow, time::Duration}; use assert_matches::assert_matches; +use assert_matches2::assert_let; use eyeball_im::VectorDiff; +use futures_util::StreamExt as _; use matrix_sdk::{ - assert_next_matches_with_timeout, config::SyncSettings, event_cache::{BackPaginationOutcome, TimelineHasBeenResetWhilePaginating}, test_utils::{ @@ -79,7 +80,7 @@ async fn test_new_pinned_events_are_added_on_sync() { ); // Load timeline items - let (items, mut timeline_stream) = timeline.subscribe().await; + let (items, mut timeline_stream) = timeline.subscribe_batched().await; assert_eq!(items.len(), 1 + 1); // event item + a date divider assert!(items[0].is_date_divider()); @@ -106,22 +107,32 @@ async fn test_new_pinned_events_are_added_on_sync() { .await .expect("Room should be synced"); + // If the test runs fast, we receive 1 update, then 4 updates. If the test runs + // slow, we receive 5 updates directly. Let's solve this flakiness with a + // `sleep`. + sleep(Duration::from_millis(500)).await; + + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 5); + // The item is added automatically - assert_next_matches_with_timeout!(timeline_stream, VectorDiff::PushBack { value } => { - assert_eq!(value.as_event().unwrap().event_id().unwrap(), event_id!("$2")); - }); + assert_let!(VectorDiff::PushBack { value } = &timeline_updates[0]); + assert_eq!(value.as_event().unwrap().event_id().unwrap(), event_id!("$2")); + // The list is reloaded, so it's reset - assert_next_matches_with_timeout!(timeline_stream, VectorDiff::Clear); + assert_let!(VectorDiff::Clear = &timeline_updates[1]); + // Then the loaded list items are added - assert_next_matches_with_timeout!(timeline_stream, VectorDiff::PushBack { value } => { - assert_eq!(value.as_event().unwrap().event_id().unwrap(), event_id!("$1")); - }); - assert_next_matches_with_timeout!(timeline_stream, VectorDiff::PushBack { value } => { - assert_eq!(value.as_event().unwrap().event_id().unwrap(), event_id!("$2")); - }); - assert_next_matches_with_timeout!(timeline_stream, VectorDiff::PushFront { value } => { - assert!(value.is_date_divider()); - }); + assert_let!(VectorDiff::PushBack { value } = &timeline_updates[2]); + assert_eq!(value.as_event().unwrap().event_id().unwrap(), event_id!("$1")); + + assert_let!(VectorDiff::PushBack { value } = &timeline_updates[3]); + assert_eq!(value.as_event().unwrap().event_id().unwrap(), event_id!("$2")); + + assert_let!(VectorDiff::PushFront { value } = &timeline_updates[4]); + assert!(value.is_date_divider()); + + assert_pending!(timeline_stream); } #[async_test] @@ -160,7 +171,7 @@ async fn test_new_pinned_event_ids_reload_the_timeline() { "there should be no live back-pagination status for a focused timeline" ); - let (items, mut timeline_stream) = timeline.subscribe().await; + let (items, mut timeline_stream) = timeline.subscribe_batched().await; assert_eq!(items.len(), 1 + 1); // event item + a date divider assert!(items[0].is_date_divider()); @@ -175,16 +186,20 @@ async fn test_new_pinned_event_ids_reload_the_timeline() { .await .expect("Sync failed"); - assert_next_matches_with_timeout!(timeline_stream, VectorDiff::Clear); - assert_next_matches_with_timeout!(timeline_stream, VectorDiff::PushBack { value } => { - assert_eq!(value.as_event().unwrap().event_id().unwrap(), event_id!("$1")); - }); - assert_next_matches_with_timeout!(timeline_stream, VectorDiff::PushBack { value } => { - assert_eq!(value.as_event().unwrap().event_id().unwrap(), event_id!("$2")); - }); - assert_next_matches_with_timeout!(timeline_stream, VectorDiff::PushFront { value } => { - assert!(value.is_date_divider()); - }); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 4); + + assert_let!(VectorDiff::Clear = &timeline_updates[0]); + + assert_let!(VectorDiff::PushBack { value } = &timeline_updates[1]); + assert_eq!(value.as_event().unwrap().event_id().unwrap(), event_id!("$1")); + + assert_let!(VectorDiff::PushBack { value } = &timeline_updates[2]); + assert_eq!(value.as_event().unwrap().event_id().unwrap(), event_id!("$2")); + + assert_let!(VectorDiff::PushFront { value } = &timeline_updates[3]); + assert!(value.is_date_divider()); + assert_pending!(timeline_stream); // Reload timeline with no pinned event @@ -195,7 +210,10 @@ async fn test_new_pinned_event_ids_reload_the_timeline() { .await .expect("Sync failed"); - assert_next_matches_with_timeout!(timeline_stream, VectorDiff::Clear); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 1); + assert_let!(VectorDiff::Clear = &timeline_updates[0]); + assert_pending!(timeline_stream); } @@ -264,7 +282,7 @@ async fn test_cached_events_are_kept_for_different_room_instances() { "there should be no live back-pagination status for a focused timeline" ); - let (items, mut timeline_stream) = timeline.subscribe().await; + let (items, mut timeline_stream) = timeline.subscribe_batched().await; assert!(!items.is_empty()); // We just loaded some events assert_pending!(timeline_stream); @@ -288,7 +306,7 @@ async fn test_cached_events_are_kept_for_different_room_instances() { let timeline = Timeline::builder(&room).with_focus(pinned_events_focus(2)).build().await.unwrap(); - let (items, _) = timeline.subscribe().await; + let (items, _) = timeline.subscribe_batched().await; assert!(!items.is_empty()); // These events came from the cache assert!(room_cache.event(event_id!("$1")).await.is_some()); @@ -349,7 +367,7 @@ async fn test_pinned_timeline_with_no_pinned_event_ids_is_just_empty() { // The timeline couldn't load any events, but it expected none, so it just // returns an empty list - let (items, _) = timeline.subscribe().await; + let (items, _) = timeline.subscribe_batched().await; assert!(items.is_empty()); } @@ -377,7 +395,7 @@ async fn test_pinned_timeline_with_no_pinned_events_and_an_utd_on_sync_is_just_e // The timeline couldn't load any events, but it expected none, so it just // returns an empty list - let (items, _) = timeline.subscribe().await; + let (items, _) = timeline.subscribe_batched().await; assert!(items.is_empty()); } @@ -400,7 +418,7 @@ async fn test_pinned_timeline_with_no_pinned_events_on_pagination_is_just_empty( // The timeline couldn't load any events, but it expected none, so it just // returns an empty list - let (pinned_items, mut pinned_events_stream) = pinned_timeline.subscribe().await; + let (pinned_items, mut pinned_events_stream) = pinned_timeline.subscribe_batched().await; assert!(pinned_items.is_empty()); // Create a non-pinned event to return in the pagination @@ -467,7 +485,7 @@ async fn test_pinned_timeline_with_pinned_utd_on_sync_contains_it() { Timeline::builder(&room).with_focus(pinned_events_focus(1)).build().await.unwrap(); // The timeline loaded with just a day divider and the pinned UTD - let (items, _) = timeline.subscribe().await; + let (items, _) = timeline.subscribe_batched().await; assert_eq!(items.len(), 2); let pinned_utd_event = items.last().unwrap().as_event().unwrap(); assert_eq!(pinned_utd_event.event_id().unwrap(), event_id); @@ -505,7 +523,7 @@ async fn test_edited_events_are_reflected_in_sync() { ); // Load timeline items - let (items, mut timeline_stream) = timeline.subscribe().await; + let (items, mut timeline_stream) = timeline.subscribe_batched().await; assert_eq!(items.len(), 1 + 1); // event item + a date divider assert!(items[0].is_date_divider()); @@ -532,26 +550,30 @@ async fn test_edited_events_are_reflected_in_sync() { .await .expect("Sync failed"); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 4); + // The list is reloaded, so it's reset - assert_next_matches_with_timeout!(timeline_stream, VectorDiff::Clear); + assert_let!(VectorDiff::Clear = &timeline_updates[0]); + // Then the loaded list items are added - assert_next_matches_with_timeout!(timeline_stream, VectorDiff::PushBack { value } => { - let event = value.as_event().unwrap(); - assert_eq!(event.event_id().unwrap(), event_id!("$1")); - }); - assert_next_matches_with_timeout!(timeline_stream, VectorDiff::PushFront { value } => { - assert!(value.is_date_divider()); - }); + assert_let!(VectorDiff::PushBack { value } = &timeline_updates[1]); + let event = value.as_event().unwrap(); + assert_eq!(event.event_id().unwrap(), event_id!("$1")); + + assert_let!(VectorDiff::PushFront { value } = &timeline_updates[2]); + assert!(value.is_date_divider()); + // The edit replaces the original event - assert_next_matches_with_timeout!(timeline_stream, VectorDiff::Set { index, value } => { - assert_eq!(index, 1); - match value.as_event().unwrap().content() { - TimelineItemContent::Message(m) => { - assert_eq!(m.body(), "* edited message!") - } - _ => panic!("Should be a message event"), + assert_let!(VectorDiff::Set { index, value } = &timeline_updates[3]); + assert_eq!(*index, 1); + match value.as_event().unwrap().content() { + TimelineItemContent::Message(m) => { + assert_eq!(m.body(), "* edited message!") } - }); + _ => panic!("Should be a message event"), + } + assert_pending!(timeline_stream); } @@ -587,7 +609,7 @@ async fn test_redacted_events_are_reflected_in_sync() { ); // Load timeline items - let (items, mut timeline_stream) = timeline.subscribe().await; + let (items, mut timeline_stream) = timeline.subscribe_batched().await; assert_eq!(items.len(), 1 + 1); // event item + a date divider assert!(items[0].is_date_divider()); @@ -610,21 +632,25 @@ async fn test_redacted_events_are_reflected_in_sync() { .await .expect("Sync failed"); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 4); + // The list is reloaded, so it's reset - assert_next_matches_with_timeout!(timeline_stream, VectorDiff::Clear); + assert_let!(VectorDiff::Clear = &timeline_updates[0]); + // Then the loaded list items are added - assert_next_matches_with_timeout!(timeline_stream, VectorDiff::PushBack { value } => { - let event = value.as_event().unwrap(); - assert_eq!(event.event_id().unwrap(), event_id!("$1")); - }); - assert_next_matches_with_timeout!(timeline_stream, VectorDiff::PushFront { value } => { - assert!(value.is_date_divider()); - }); + assert_let!(VectorDiff::PushBack { value } = &timeline_updates[1]); + let event = value.as_event().unwrap(); + assert_eq!(event.event_id().unwrap(), event_id!("$1")); + + assert_let!(VectorDiff::PushFront { value } = &timeline_updates[2]); + assert!(value.is_date_divider()); + // The redaction replaces the original event - assert_next_matches_with_timeout!(timeline_stream, VectorDiff::Set { index, value } => { - assert_eq!(index, 1); - assert_matches!(value.as_event().unwrap().content(), TimelineItemContent::RedactedMessage); - }); + assert_let!(VectorDiff::Set { index, value } = &timeline_updates[3]); + assert_eq!(*index, 1); + assert_matches!(value.as_event().unwrap().content(), TimelineItemContent::RedactedMessage); + assert_pending!(timeline_stream); } @@ -659,7 +685,7 @@ async fn test_edited_events_survive_pinned_event_ids_change() { ); // Load timeline items - let (items, mut timeline_stream) = timeline.subscribe().await; + let (items, mut timeline_stream) = timeline.subscribe_batched().await; assert_eq!(items.len(), 1 + 1); // event item + a date divider assert!(items[0].is_date_divider()); @@ -687,26 +713,30 @@ async fn test_edited_events_survive_pinned_event_ids_change() { .await .expect("Sync failed"); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 4); + // The list is reloaded, so it's reset - assert_next_matches_with_timeout!(timeline_stream, VectorDiff::Clear); + assert_let!(VectorDiff::Clear = &timeline_updates[0]); + // Then the loaded list items are added - assert_next_matches_with_timeout!(timeline_stream, VectorDiff::PushBack { value } => { - let event = value.as_event().unwrap(); - assert_eq!(event.event_id().unwrap(), event_id!("$1")); - }); - assert_next_matches_with_timeout!(timeline_stream, VectorDiff::PushFront { value } => { - assert!(value.is_date_divider()); - }); + assert_let!(VectorDiff::PushBack { value } = &timeline_updates[1]); + let event = value.as_event().unwrap(); + assert_eq!(event.event_id().unwrap(), event_id!("$1")); + + assert_let!(VectorDiff::PushFront { value } = &timeline_updates[2]); + assert!(value.is_date_divider()); + // The edit replaces the original event - assert_next_matches_with_timeout!(timeline_stream, VectorDiff::Set { index, value } => { - assert_eq!(index, 1); - match value.as_event().unwrap().content() { - TimelineItemContent::Message(m) => { - assert_eq!(m.body(), "edited message!") - } - _ => panic!("Should be a message event"), + assert_let!(VectorDiff::Set { index, value } = &timeline_updates[3]); + assert_eq!(*index, 1); + match value.as_event().unwrap().content() { + TimelineItemContent::Message(m) => { + assert_eq!(m.body(), "edited message!") } - }); + _ => panic!("Should be a message event"), + } + assert_pending!(timeline_stream); let new_pinned_event = f @@ -726,36 +756,44 @@ async fn test_edited_events_survive_pinned_event_ids_change() { .await .expect("Sync failed"); + // If the test runs fast, we receive 1 update, then 5 updates. If the test runs + // slow, we receive 6 updates directly. Let's solve this flakiness with a + // `sleep`. + sleep(Duration::from_millis(500)).await; + + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 6); + // New item gets added - assert_next_matches_with_timeout!(timeline_stream, VectorDiff::PushBack { value } => { - let event = value.as_event().unwrap(); - assert_eq!(event.event_id().unwrap(), event_id!("$3")); - }); + assert_let!(VectorDiff::PushBack { value } = &timeline_updates[0]); + let event = value.as_event().unwrap(); + assert_eq!(event.event_id().unwrap(), event_id!("$3")); + // The list is reloaded, so it's reset - assert_next_matches_with_timeout!(timeline_stream, VectorDiff::Clear); + assert_let!(VectorDiff::Clear = &timeline_updates[1]); + // Then the loaded list items are added - assert_next_matches_with_timeout!(timeline_stream, VectorDiff::PushBack { value } => { - let event = value.as_event().unwrap(); - assert_eq!(event.event_id().unwrap(), event_id!("$1")); - }); + assert_let!(VectorDiff::PushBack { value } = &timeline_updates[2]); + let event = value.as_event().unwrap(); + assert_eq!(event.event_id().unwrap(), event_id!("$1")); + // The edit replaces the original event - assert_next_matches_with_timeout!(timeline_stream, VectorDiff::Set { index, value } => { - assert_eq!(index, 0); - match value.as_event().unwrap().content() { - TimelineItemContent::Message(m) => { - assert_eq!(m.body(), "edited message!") - } - _ => panic!("Should be a message event"), + assert_let!(VectorDiff::Set { index, value } = &timeline_updates[3]); + assert_eq!(*index, 0); + match value.as_event().unwrap().content() { + TimelineItemContent::Message(m) => { + assert_eq!(m.body(), "edited message!") } - }); + _ => panic!("Should be a message event"), + } // The new pinned event is added - assert_next_matches_with_timeout!(timeline_stream, VectorDiff::PushBack { value } => { - let event = value.as_event().unwrap(); - assert_eq!(event.event_id().unwrap(), event_id!("$3")); - }); - assert_next_matches_with_timeout!(timeline_stream, VectorDiff::PushFront { value } => { - assert!(value.is_date_divider()); - }); + assert_let!(VectorDiff::PushBack { value } = &timeline_updates[4]); + let event = value.as_event().unwrap(); + assert_eq!(event.event_id().unwrap(), event_id!("$3")); + + assert_let!(VectorDiff::PushFront { value } = &timeline_updates[5]); + assert!(value.is_date_divider()); + assert_pending!(timeline_stream); } diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/queue.rs b/crates/matrix-sdk-ui/tests/integration/timeline/queue.rs index 526ce50d8..08184b1ba 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/queue.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/queue.rs @@ -329,15 +329,20 @@ async fn test_clear_with_echoes() { // Send a message without mocking the server response. { - let (_, mut timeline_stream) = timeline.subscribe().await; + let (_, mut timeline_stream) = timeline.subscribe_batched().await; timeline.send(RoomMessageEventContent::text_plain("Send failure").into()).await.unwrap(); // Wait for the first message to fail. Don't use time, but listen for the first // timeline item diff to get back signalling the error. - let _date_divider = timeline_stream.next().await; - let _local_echo = timeline_stream.next().await; - let _local_echo_replaced_with_failure = timeline_stream.next().await; + + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + // 2 updates: date divider and local echo. + assert_eq!(timeline_updates.len(), 2); + + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + // 1 updates: local echo replaced with failure. + assert_eq!(timeline_updates.len(), 1); } // Next message will take "forever" to send. @@ -403,7 +408,7 @@ async fn test_no_duplicate_date_divider() { let room = client.get_room(room_id).unwrap(); let timeline = Arc::new(room.timeline().await.unwrap()); - let (_, mut timeline_stream) = timeline.subscribe().await; + let (_, mut timeline_stream) = timeline.subscribe_batched().await; // Response for first message takes 200ms to respond. Mock::given(method("PUT")) @@ -440,35 +445,36 @@ async fn test_no_duplicate_date_divider() { // Let the send queue handle the event. yield_now().await; + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 3); + // Local echoes are available as soon as `timeline.send` returns. - assert_next_matches!(timeline_stream, VectorDiff::PushBack { value } => { - assert_eq!(value.as_event().unwrap().content().as_message().unwrap().body(), "First!"); - }); + assert_let!(VectorDiff::PushBack { value } = &timeline_updates[0]); + assert_eq!(value.as_event().unwrap().content().as_message().unwrap().body(), "First!"); - assert_next_matches!(timeline_stream, VectorDiff::PushFront { value } => { - assert!(value.is_date_divider()); - }); + assert_let!(VectorDiff::PushFront { value } = &timeline_updates[1]); + assert!(value.is_date_divider()); - assert_next_matches!(timeline_stream, VectorDiff::PushBack { value } => { - assert_eq!(value.as_event().unwrap().content().as_message().unwrap().body(), "Second."); - }); + assert_let!(VectorDiff::PushBack { value } = &timeline_updates[2]); + assert_eq!(value.as_event().unwrap().content().as_message().unwrap().body(), "Second."); // Wait 200ms for the first msg, 100ms for the second, 200ms for overhead. sleep(Duration::from_millis(500)).await; + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 2); + // The first item should be updated first. - assert_next_matches!(timeline_stream, VectorDiff::Set { index: 1, value } => { - let value = value.as_event().unwrap(); - assert_eq!(value.content().as_message().unwrap().body(), "First!"); - assert_eq!(value.event_id().unwrap(), "$PyHxV5mYzjetBUT3qZq7V95GOzxb02EP"); - }); + assert_let!(VectorDiff::Set { index: 1, value } = &timeline_updates[0]); + let value = value.as_event().unwrap(); + assert_eq!(value.content().as_message().unwrap().body(), "First!"); + assert_eq!(value.event_id().unwrap(), "$PyHxV5mYzjetBUT3qZq7V95GOzxb02EP"); // Then the second one. - assert_next_matches!(timeline_stream, VectorDiff::Set { index: 2, value } => { - let value = value.as_event().unwrap(); - assert_eq!(value.content().as_message().unwrap().body(), "Second."); - assert_eq!(value.event_id().unwrap(), "$5E2kLK/Sg342bgBU9ceEIEPYpbFaqJpZ"); - }); + assert_let!(VectorDiff::Set { index: 2, value } = &timeline_updates[1]); + let value = value.as_event().unwrap(); + assert_eq!(value.content().as_message().unwrap().body(), "Second."); + assert_eq!(value.event_id().unwrap(), "$5E2kLK/Sg342bgBU9ceEIEPYpbFaqJpZ"); assert_pending!(timeline_stream); @@ -496,31 +502,32 @@ async fn test_no_duplicate_date_divider() { let _response = client.sync_once(sync_settings.clone()).await.unwrap(); server.reset().await; + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 6); + // The first message is removed -> [DD Second] - assert_next_matches!(timeline_stream, VectorDiff::Remove { index: 1 }); + assert_let!(VectorDiff::Remove { index: 1 } = &timeline_updates[0]); // The first message is reinserted -> [First DD Second] - assert_next_matches!(timeline_stream, VectorDiff::PushFront { value } => { - let value = value.as_event().unwrap(); - assert_eq!(value.content().as_message().unwrap().body(), "First!"); - assert_eq!(value.event_id().unwrap(), "$PyHxV5mYzjetBUT3qZq7V95GOzxb02EP"); - }); + assert_let!(VectorDiff::PushFront { value } = &timeline_updates[1]); + let value = value.as_event().unwrap(); + assert_eq!(value.content().as_message().unwrap().body(), "First!"); + assert_eq!(value.event_id().unwrap(), "$PyHxV5mYzjetBUT3qZq7V95GOzxb02EP"); // The second message is replaced -> [First Second DD] - assert_next_matches!(timeline_stream, VectorDiff::Remove { index: 2 }); - assert_next_matches!(timeline_stream, VectorDiff::Insert { index: 1, value } => { - let value = value.as_event().unwrap(); - assert_eq!(value.content().as_message().unwrap().body(), "Second."); - assert_eq!(value.event_id().unwrap(), "$5E2kLK/Sg342bgBU9ceEIEPYpbFaqJpZ"); - }); + assert_let!(VectorDiff::Remove { index: 2 } = &timeline_updates[2]); + + assert_let!(VectorDiff::Insert { index: 1, value } = &timeline_updates[3]); + let value = value.as_event().unwrap(); + assert_eq!(value.content().as_message().unwrap().body(), "Second."); + assert_eq!(value.event_id().unwrap(), "$5E2kLK/Sg342bgBU9ceEIEPYpbFaqJpZ"); // A new date divider is inserted -> [DD First Second DD] - assert_next_matches!(timeline_stream, VectorDiff::PushFront { value } => { - assert!(value.is_date_divider()); - }); + assert_let!(VectorDiff::PushFront { value } = &timeline_updates[4]); + assert!(value.is_date_divider()); // The useless date divider is removed. -> [DD First Second] - assert_next_matches!(timeline_stream, VectorDiff::Remove { index: 3 }); + assert_let!(VectorDiff::Remove { index: 3 } = &timeline_updates[5]); assert_pending!(timeline_stream); } diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/reactions.rs b/crates/matrix-sdk-ui/tests/integration/timeline/reactions.rs index ff2e5bf33..23dc827b2 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/reactions.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/reactions.rs @@ -17,10 +17,7 @@ use std::{sync::Mutex, time::Duration}; use assert_matches2::{assert_let, assert_matches}; use eyeball_im::VectorDiff; use futures_util::{FutureExt as _, StreamExt as _}; -use matrix_sdk::{ - assert_next_matches_with_timeout, - test_utils::{logged_in_client_with_server, mocks::MatrixMockServer}, -}; +use matrix_sdk::test_utils::{logged_in_client_with_server, mocks::MatrixMockServer}; use matrix_sdk_test::{ async_test, event_factory::EventFactory, mocks::mock_encryption_state, JoinedRoomBuilder, SyncResponseBuilder, ALICE, @@ -28,6 +25,7 @@ use matrix_sdk_test::{ use matrix_sdk_ui::timeline::{ReactionStatus, RoomExt as _}; use ruma::{event_id, events::room::message::RoomMessageEventContent, room_id}; use serde_json::json; +use stream_assert::assert_pending; use wiremock::{ matchers::{header, method, path_regex}, Mock, ResponseTemplate, @@ -49,7 +47,7 @@ async fn test_abort_before_being_sent() { server.mock_room_state_encryption().plain().mount().await; let timeline = room.timeline().await.unwrap(); - let (initial_items, mut stream) = timeline.subscribe().await; + let (initial_items, mut stream) = timeline.subscribe_batched().await; assert!(initial_items.is_empty()); @@ -64,12 +62,15 @@ async fn test_abort_before_being_sent() { ) .await; - assert_let!(Some(VectorDiff::PushBack { value: first }) = stream.next().await); + assert_let!(Some(timeline_updates) = stream.next().await); + assert_eq!(timeline_updates.len(), 2); + + assert_let!(VectorDiff::PushBack { value: first } = &timeline_updates[0]); let item = first.as_event().unwrap(); let item_id = item.identifier(); assert_eq!(item.content().as_message().unwrap().body(), "hello"); - assert_let!(Some(VectorDiff::PushFront { value: date_divider }) = stream.next().await); + assert_let!(VectorDiff::PushFront { value: date_divider } = &timeline_updates[1]); assert!(date_divider.is_date_divider()); // Now we try to add two reactions to this message… @@ -98,7 +99,10 @@ async fn test_abort_before_being_sent() { // First toggle (local echo). { - assert_let!(Some(VectorDiff::Set { index: 1, value: item }) = stream.next().await); + assert_let!(Some(timeline_updates) = stream.next().await); + assert_eq!(timeline_updates.len(), 1); + + assert_let!(VectorDiff::Set { index: 1, value: item } = &timeline_updates[0]); let reactions = item.as_event().unwrap().reactions(); assert_eq!(reactions.len(), 1); @@ -107,14 +111,17 @@ async fn test_abort_before_being_sent() { ReactionStatus::LocalToRemote(_) ); - assert!(stream.next().now_or_never().is_none()); + assert_pending!(stream); } // We toggle another reaction at the same time… timeline.toggle_reaction(&item_id, "🥰").await.unwrap(); { - assert_let!(Some(VectorDiff::Set { index: 1, value: item }) = stream.next().await); + assert_let!(Some(timeline_updates) = stream.next().await); + assert_eq!(timeline_updates.len(), 1); + + assert_let!(VectorDiff::Set { index: 1, value: item } = &timeline_updates[0]); let reactions = item.as_event().unwrap().reactions(); assert_eq!(reactions.len(), 2); @@ -135,7 +142,10 @@ async fn test_abort_before_being_sent() { timeline.toggle_reaction(&item_id, "👍").await.unwrap(); { - assert_let!(Some(VectorDiff::Set { index: 1, value: item }) = stream.next().await); + assert_let!(Some(timeline_updates) = stream.next().await); + assert_eq!(timeline_updates.len(), 1); + + assert_let!(VectorDiff::Set { index: 1, value: item } = &timeline_updates[0]); let reactions = item.as_event().unwrap().reactions(); assert_eq!(reactions.len(), 1); @@ -152,7 +162,10 @@ async fn test_abort_before_being_sent() { timeline.toggle_reaction(&item_id, "🥰").await.unwrap(); { - assert_let!(Some(VectorDiff::Set { index: 1, value: item }) = stream.next().await); + assert_let!(Some(timeline_updates) = stream.next().await); + assert_eq!(timeline_updates.len(), 1); + + assert_let!(VectorDiff::Set { index: 1, value: item } = &timeline_updates[0]); let reactions = item.as_event().unwrap().reactions(); assert!(reactions.is_empty()); @@ -165,7 +178,7 @@ async fn test_abort_before_being_sent() { // redaction of the reaction. In our case, we're done here. tokio::time::sleep(Duration::from_millis(300)).await; - assert!(stream.next().now_or_never().is_none()); + assert_pending!(stream); } #[async_test] @@ -189,7 +202,7 @@ async fn test_redact_failed() { let room = client.get_room(room_id).unwrap(); let timeline = room.timeline().await.unwrap(); - let (initial_items, mut stream) = timeline.subscribe().await; + let (initial_items, mut stream) = timeline.subscribe_batched().await; assert!(initial_items.is_empty()); @@ -206,20 +219,24 @@ async fn test_redact_failed() { let _response = client.sync_once(Default::default()).await.unwrap(); server.reset().await; - let item_id = assert_next_matches_with_timeout!(stream, VectorDiff::PushBack { value: item } => { + assert_let!(Some(timeline_updates) = stream.next().await); + assert_eq!(timeline_updates.len(), 3); + + let item_id = { + assert_let!(VectorDiff::PushBack { value: item } = &timeline_updates[0]); + let item = item.as_event().unwrap(); assert_eq!(item.content().as_message().unwrap().body(), "hello"); assert!(item.reactions().is_empty()); + item.identifier() - }); + }; - assert_next_matches_with_timeout!(stream, VectorDiff::Set { index: 0, value: item } => { - assert_eq!(item.as_event().unwrap().reactions().len(), 1); - }); + assert_let!(VectorDiff::Set { index: 0, value: item } = &timeline_updates[1]); + assert_eq!(item.as_event().unwrap().reactions().len(), 1); - assert_next_matches_with_timeout!(stream, VectorDiff::PushFront { value: date_divider } => { - assert!(date_divider.is_date_divider()); - }); + assert_let!(VectorDiff::PushFront { value: date_divider } = &timeline_updates[2]); + assert!(date_divider.is_date_divider()); // Now, redact the annotation we previously added. @@ -235,18 +252,19 @@ async fn test_redact_failed() { // We toggle the reaction, which fails with an error. timeline.toggle_reaction(&item_id, "😆").await.unwrap_err(); + assert_let!(Some(timeline_updates) = stream.next().await); + assert_eq!(timeline_updates.len(), 2); + // The local echo is removed (assuming the redaction works)… - assert_next_matches_with_timeout!(stream, VectorDiff::Set { index: 1, value: item } => { - assert!(item.as_event().unwrap().reactions().is_empty()); - }); + assert_let!(VectorDiff::Set { index: 1, value: item } = &timeline_updates[0]); + assert!(item.as_event().unwrap().reactions().is_empty()); // …then added back, after redaction failed. - assert_next_matches_with_timeout!(stream, VectorDiff::Set { index: 1, value: item } => { - assert_eq!(item.as_event().unwrap().reactions().len(), 1); - }); + assert_let!(VectorDiff::Set { index: 1, value: item } = &timeline_updates[1]); + assert_eq!(item.as_event().unwrap().reactions().len(), 1); tokio::time::sleep(Duration::from_millis(150)).await; - assert!(stream.next().now_or_never().is_none()); + assert_pending!(stream); } #[async_test] @@ -270,7 +288,7 @@ async fn test_local_reaction_to_local_echo() { let room = client.get_room(room_id).unwrap(); let timeline = room.timeline().await.unwrap(); - let (initial_items, mut stream) = timeline.subscribe().await; + let (initial_items, mut stream) = timeline.subscribe_batched().await; assert!(initial_items.is_empty()); @@ -300,73 +318,88 @@ async fn test_local_reaction_to_local_echo() { // Send a local event. let _ = timeline.send(RoomMessageEventContent::text_plain("lol").into()).await.unwrap(); + assert_let!(Some(timeline_updates) = stream.next().await); + assert_eq!(timeline_updates.len(), 2); + // Receive a local echo. - let item_id = assert_next_matches_with_timeout!(stream, VectorDiff::PushBack { value: item } => { + + let item_id = { + assert_let!(VectorDiff::PushBack { value: item } = &timeline_updates[0]); + let item = item.as_event().unwrap(); assert!(item.is_local_echo()); assert_eq!(item.content().as_message().unwrap().body(), "lol"); assert!(item.reactions().is_empty()); item.identifier() - }); + }; // Good ol' date divider. - assert_next_matches_with_timeout!(stream, VectorDiff::PushFront { value: date_divider } => { - assert!(date_divider.is_date_divider()); - }); + assert_let!(VectorDiff::PushFront { value: date_divider } = &timeline_updates[1]); + assert!(date_divider.is_date_divider()); // Add a reaction before the remote echo comes back. let key1 = "🤣"; timeline.toggle_reaction(&item_id, key1).await.unwrap(); + assert_let!(Some(timeline_updates) = stream.next().await); + assert_eq!(timeline_updates.len(), 1); + // The reaction is added to the local echo. - assert_next_matches_with_timeout!(stream, VectorDiff::Set { index: 1, value: item } => { - let reactions = item.as_event().unwrap().reactions(); - assert_eq!(reactions.len(), 1); - let reaction_info = reactions.get(key1).unwrap().get(user_id).unwrap(); - assert_matches!(&reaction_info.status, ReactionStatus::LocalToLocal(..)); - }); + assert_let!(VectorDiff::Set { index: 1, value: item } = &timeline_updates[0]); + let reactions = item.as_event().unwrap().reactions(); + assert_eq!(reactions.len(), 1); + let reaction_info = reactions.get(key1).unwrap().get(user_id).unwrap(); + assert_matches!(&reaction_info.status, ReactionStatus::LocalToLocal(..)); // Add another reaction. let key2 = "😈"; timeline.toggle_reaction(&item_id, key2).await.unwrap(); + assert_let!(Some(timeline_updates) = stream.next().await); + assert_eq!(timeline_updates.len(), 1); + // Also comes as a local echo. - assert_next_matches_with_timeout!(stream, VectorDiff::Set { index: 1, value: item } => { - let reactions = item.as_event().unwrap().reactions(); - assert_eq!(reactions.len(), 2); - let reaction_info = reactions.get(key2).unwrap().get(user_id).unwrap(); - assert_matches!(&reaction_info.status, ReactionStatus::LocalToLocal(..)); - }); + assert_let!(VectorDiff::Set { index: 1, value: item } = &timeline_updates[0]); + let reactions = item.as_event().unwrap().reactions(); + assert_eq!(reactions.len(), 2); + let reaction_info = reactions.get(key2).unwrap().get(user_id).unwrap(); + assert_matches!(&reaction_info.status, ReactionStatus::LocalToLocal(..)); // Remove second reaction. It's immediately removed, since it was a local echo, // and it wasn't being sent. timeline.toggle_reaction(&item_id, key2).await.unwrap(); - assert_next_matches_with_timeout!(stream, VectorDiff::Set { index: 1, value: item } => { - let reactions = item.as_event().unwrap().reactions(); - assert_eq!(reactions.len(), 1); - let reaction_info = reactions.get(key1).unwrap().get(user_id).unwrap(); - assert_matches!(&reaction_info.status, ReactionStatus::LocalToLocal(..)); - }); + assert_let!(Some(timeline_updates) = stream.next().await); + assert_eq!(timeline_updates.len(), 1); + + assert_let!(VectorDiff::Set { index: 1, value: item } = &timeline_updates[0]); + let reactions = item.as_event().unwrap().reactions(); + assert_eq!(reactions.len(), 1); + let reaction_info = reactions.get(key1).unwrap().get(user_id).unwrap(); + assert_matches!(&reaction_info.status, ReactionStatus::LocalToLocal(..)); + + assert_let!(Some(timeline_updates) = stream.next().await); + assert_eq!(timeline_updates.len(), 1); // Now, wait for the remote echo for the message itself. - assert_next_matches_with_timeout!(stream, 2000, VectorDiff::Set { index: 1, value: item } => { - let reactions = item.as_event().unwrap().reactions(); - assert_eq!(reactions.len(), 1); - let reaction_info = reactions.get(key1).unwrap().get(user_id).unwrap(); - // TODO(bnjbvr): why not LocalToRemote here? - assert_matches!(&reaction_info.status, ReactionStatus::LocalToLocal(..)); - }); + assert_let!(VectorDiff::Set { index: 1, value: item } = &timeline_updates[0]); + let reactions = item.as_event().unwrap().reactions(); + assert_eq!(reactions.len(), 1); + let reaction_info = reactions.get(key1).unwrap().get(user_id).unwrap(); + // TODO(bnjbvr): why not LocalToRemote here? + assert_matches!(&reaction_info.status, ReactionStatus::LocalToLocal(..)); + + assert_let!(Some(timeline_updates) = stream.next().await); + assert_eq!(timeline_updates.len(), 1); // And then the remote echo for the reaction itself. - assert_next_matches_with_timeout!(stream, VectorDiff::Set { index: 1, value: item } => { - let reactions = item.as_event().unwrap().reactions(); - assert_eq!(reactions.len(), 1); - let reaction_info = reactions.get(key1).unwrap().get(user_id).unwrap(); - assert_matches!(&reaction_info.status, ReactionStatus::RemoteToRemote(..)); - }); + assert_let!(VectorDiff::Set { index: 1, value: item } = &timeline_updates[0]); + let reactions = item.as_event().unwrap().reactions(); + assert_eq!(reactions.len(), 1); + let reaction_info = reactions.get(key1).unwrap().get(user_id).unwrap(); + assert_matches!(&reaction_info.status, ReactionStatus::RemoteToRemote(..)); // And we're done. tokio::time::sleep(Duration::from_millis(150)).await; - assert!(stream.next().now_or_never().is_none()); + assert_pending!(stream); } diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/read_receipts.rs b/crates/matrix-sdk-ui/tests/integration/timeline/read_receipts.rs index 22e39defb..8a36c89c3 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/read_receipts.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/read_receipts.rs @@ -76,7 +76,7 @@ async fn test_read_receipts_updates() { let room = client.get_room(room_id).unwrap(); let timeline = room.timeline().await.unwrap(); - let (items, mut timeline_stream) = timeline.subscribe().await; + let (items, mut timeline_stream) = timeline.subscribe_batched().await; let mut own_receipts_subscriber = timeline.subscribe_own_user_read_receipts_changed().await; assert!(items.is_empty()); @@ -132,8 +132,11 @@ async fn test_read_receipts_updates() { let _response = client.sync_once(sync_settings.clone()).await.unwrap(); server.reset().await; + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 5); + // We don't list the read receipt of our own user on events. - assert_let!(Some(VectorDiff::PushBack { value: first_item }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushBack { value: first_item } = &timeline_updates[0]); let first_event = first_item.as_event().unwrap(); assert!(first_event.read_receipts().is_empty()); @@ -144,25 +147,23 @@ async fn test_read_receipts_updates() { assert_pending!(own_receipts_subscriber); // Implicit read receipt of @alice:localhost. - assert_let!(Some(VectorDiff::PushBack { value: second_item }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushBack { value: second_item } = &timeline_updates[1]); let second_event = second_item.as_event().unwrap(); assert_eq!(second_event.read_receipts().len(), 1); // Read receipt of @alice:localhost is moved to third event. - assert_let!( - Some(VectorDiff::Set { index: 1, value: second_item }) = timeline_stream.next().await - ); + assert_let!(VectorDiff::Set { index: 1, value: second_item } = &timeline_updates[2]); let second_event = second_item.as_event().unwrap(); assert!(second_event.read_receipts().is_empty()); - assert_let!(Some(VectorDiff::PushBack { value: third_item }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushBack { value: third_item } = &timeline_updates[3]); let third_event = third_item.as_event().unwrap(); assert_eq!(third_event.read_receipts().len(), 1); let (alice_receipt_event_id, _) = timeline.latest_user_read_receipt(alice).await.unwrap(); assert_eq!(alice_receipt_event_id, third_event_id); - assert_let!(Some(VectorDiff::PushFront { value: date_divider }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushFront { value: date_divider } = &timeline_updates[4]); assert!(date_divider.is_date_divider()); // Read receipt on unknown event is ignored. @@ -254,9 +255,10 @@ async fn test_read_receipts_updates() { let _response = client.sync_once(sync_settings.clone()).await.unwrap(); server.reset().await; - assert_let!( - Some(VectorDiff::Set { index: 3, value: third_item }) = timeline_stream.next().await - ); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 1); + + assert_let!(VectorDiff::Set { index: 3, value: third_item } = &timeline_updates[0]); let third_event = third_item.as_event().unwrap(); assert_eq!(third_event.read_receipts().len(), 2); @@ -291,6 +293,7 @@ async fn test_read_receipts_updates() { assert_ready!(own_receipts_subscriber); assert_pending!(own_receipts_subscriber); + assert_pending!(timeline_stream); } #[async_test] @@ -316,7 +319,7 @@ async fn test_read_receipts_updates_on_filtered_events() { let room = client.get_room(room_id).unwrap(); let timeline = room.timeline_builder().event_filter(filter_notice).build().await.unwrap(); - let (items, mut timeline_stream) = timeline.subscribe().await; + let (items, mut timeline_stream) = timeline.subscribe_batched().await; assert!(items.is_empty()); @@ -377,8 +380,11 @@ async fn test_read_receipts_updates_on_filtered_events() { let _response = client.sync_once(sync_settings.clone()).await.unwrap(); server.reset().await; + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 4); + // We don't list the read receipt of our own user on events. - assert_let!(Some(VectorDiff::PushBack { value: item_a }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushBack { value: item_a } = &timeline_updates[0]); let event_a = item_a.as_event().unwrap(); assert!(event_a.read_receipts().is_empty()); @@ -389,7 +395,7 @@ async fn test_read_receipts_updates_on_filtered_events() { assert_eq!(own_receipt_timeline_event, event_a_id); // Implicit read receipt of @bob:localhost. - assert_let!(Some(VectorDiff::Set { index: 0, value: item_a }) = timeline_stream.next().await); + assert_let!(VectorDiff::Set { index: 0, value: item_a } = &timeline_updates[1]); let event_a = item_a.as_event().unwrap(); assert_eq!(event_a.read_receipts().len(), 1); @@ -402,7 +408,7 @@ async fn test_read_receipts_updates_on_filtered_events() { assert_eq!(bob_receipt_timeline_event, event_a.event_id().unwrap()); // Implicit read receipt of @alice:localhost. - assert_let!(Some(VectorDiff::PushBack { value: item_c }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushBack { value: item_c } = &timeline_updates[2]); let event_c = item_c.as_event().unwrap(); assert_eq!(event_c.read_receipts().len(), 1); @@ -412,7 +418,7 @@ async fn test_read_receipts_updates_on_filtered_events() { timeline.latest_user_read_receipt_timeline_event_id(*ALICE).await.unwrap(); assert_eq!(alice_receipt_timeline_event, event_c_id); - assert_let!(Some(VectorDiff::PushFront { value: date_divider }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushFront { value: date_divider } = &timeline_updates[3]); assert!(date_divider.is_date_divider()); // Read receipt on filtered event. @@ -463,11 +469,14 @@ async fn test_read_receipts_updates_on_filtered_events() { let _response = client.sync_once(sync_settings.clone()).await.unwrap(); server.reset().await; - assert_let!(Some(VectorDiff::Set { index: 1, value: item_a }) = timeline_stream.next().await); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 2); + + assert_let!(VectorDiff::Set { index: 1, value: item_a } = &timeline_updates[0]); let event_a = item_a.as_event().unwrap(); assert!(event_a.read_receipts().is_empty()); - assert_let!(Some(VectorDiff::Set { index: 2, value: item_c }) = timeline_stream.next().await); + assert_let!(VectorDiff::Set { index: 2, value: item_c } = &timeline_updates[1]); let event_c = item_c.as_event().unwrap(); assert_eq!(event_c.read_receipts().len(), 2); @@ -505,6 +514,7 @@ async fn test_read_receipts_updates_on_filtered_events() { let own_receipt_timeline_event = timeline.latest_user_read_receipt_timeline_event_id(own_user_id).await.unwrap(); assert_eq!(own_receipt_timeline_event, event_c_id); + assert_pending!(timeline_stream); } #[async_test] @@ -1196,7 +1206,7 @@ async fn test_latest_user_read_receipt() { let room = client.get_room(room_id).unwrap(); let timeline = room.timeline().await.unwrap(); - let (items, _) = timeline.subscribe().await; + let (items, _) = timeline.subscribe_batched().await; assert!(items.is_empty()); diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/replies.rs b/crates/matrix-sdk-ui/tests/integration/timeline/replies.rs index 82450c1a8..592df6ba7 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/replies.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/replies.rs @@ -24,7 +24,7 @@ use ruma::{ owned_event_id, room_id, }; use serde_json::json; -use stream_assert::assert_next_matches; +use stream_assert::{assert_next_matches, assert_pending}; use tokio::task::yield_now; use wiremock::{ matchers::{header, method, path_regex}, @@ -50,7 +50,7 @@ async fn test_in_reply_to_details() { let room = client.get_room(room_id).unwrap(); let timeline = room.timeline().await.unwrap(); - let (_, mut timeline_stream) = timeline.subscribe().await; + let (_, mut timeline_stream) = timeline.subscribe_batched().await; // The event doesn't exist. assert_matches!( @@ -71,17 +71,20 @@ async fn test_in_reply_to_details() { let _response = client.sync_once(sync_settings.clone()).await.unwrap(); server.reset().await; - assert_let!(Some(VectorDiff::PushBack { value: first }) = timeline_stream.next().await); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 3); + + assert_let!(VectorDiff::PushBack { value: first } = &timeline_updates[0]); assert_matches!(first.as_event().unwrap().content(), TimelineItemContent::Message(_)); - assert_let!(Some(VectorDiff::PushBack { value: second }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushBack { value: second } = &timeline_updates[1]); let second_event = second.as_event().unwrap(); assert_let!(TimelineItemContent::Message(message) = second_event.content()); let in_reply_to = message.in_reply_to().unwrap(); assert_eq!(in_reply_to.event_id, event_id!("$event1")); assert_matches!(in_reply_to.event, TimelineDetails::Ready(_)); - assert_let!(Some(VectorDiff::PushFront { value: date_divider }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushFront { value: date_divider } = &timeline_updates[2]); assert!(date_divider.is_date_divider()); // Add an reply to an unknown event to the timeline @@ -95,11 +98,12 @@ async fn test_in_reply_to_details() { let _response = client.sync_once(sync_settings.clone()).await.unwrap(); server.reset().await; - assert_let!( - Some(VectorDiff::Set { value: _read_receipt_update, .. }) = timeline_stream.next().await - ); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 2); - assert_let!(Some(VectorDiff::PushBack { value: third }) = timeline_stream.next().await); + assert_let!(VectorDiff::Set { value: _read_receipt_update, .. } = &timeline_updates[0]); + + assert_let!(VectorDiff::PushBack { value: third } = &timeline_updates[1]); let third_event = third.as_event().unwrap(); assert_let!(TimelineItemContent::Message(message) = third_event.content()); let in_reply_to = message.in_reply_to().unwrap(); @@ -124,12 +128,15 @@ async fn test_in_reply_to_details() { timeline.fetch_details_for_event(third_event.event_id().unwrap()).await.unwrap(); server.reset().await; - assert_let!(Some(VectorDiff::Set { index: 3, value: third }) = timeline_stream.next().await); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 2); + + assert_let!(VectorDiff::Set { index: 3, value: third } = &timeline_updates[0]); assert_let!(TimelineItemContent::Message(message) = third.as_event().unwrap().content()); assert_matches!(message.in_reply_to().unwrap().event, TimelineDetails::Pending); assert_eq!(*third.unique_id(), unique_id); - assert_let!(Some(VectorDiff::Set { index: 3, value: third }) = timeline_stream.next().await); + assert_let!(VectorDiff::Set { index: 3, value: third } = &timeline_updates[1]); assert_let!(TimelineItemContent::Message(message) = third.as_event().unwrap().content()); assert_matches!(message.in_reply_to().unwrap().event, TimelineDetails::Error(_)); assert_eq!(*third.unique_id(), unique_id); @@ -153,15 +160,20 @@ async fn test_in_reply_to_details() { timeline.fetch_details_for_event(third_event.event_id().unwrap()).await.unwrap(); - assert_let!(Some(VectorDiff::Set { index: 3, value: third }) = timeline_stream.next().await); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 2); + + assert_let!(VectorDiff::Set { index: 3, value: third } = &timeline_updates[0]); assert_let!(TimelineItemContent::Message(message) = third.as_event().unwrap().content()); assert_matches!(message.in_reply_to().unwrap().event, TimelineDetails::Pending); assert_eq!(*third.unique_id(), unique_id); - assert_let!(Some(VectorDiff::Set { index: 3, value: third }) = timeline_stream.next().await); + assert_let!(VectorDiff::Set { index: 3, value: third } = &timeline_updates[1]); assert_let!(TimelineItemContent::Message(message) = third.as_event().unwrap().content()); assert_matches!(message.in_reply_to().unwrap().event, TimelineDetails::Ready(_)); assert_eq!(*third.unique_id(), unique_id); + + assert_pending!(timeline_stream); } #[async_test] diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/sliding_sync.rs b/crates/matrix-sdk-ui/tests/integration/timeline/sliding_sync.rs index f2fdc800d..161daa264 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/sliding_sync.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/sliding_sync.rs @@ -18,7 +18,7 @@ use anyhow::{Context as _, Result}; use assert_matches::assert_matches; use assert_matches2::assert_let; use eyeball_im::{Vector, VectorDiff}; -use futures_util::{pin_mut, FutureExt, Stream, StreamExt}; +use futures_util::{pin_mut, Stream, StreamExt}; use matrix_sdk::{ test_utils::logged_in_client_with_server, Client, SlidingSync, SlidingSyncList, SlidingSyncListBuilder, SlidingSyncMode, UpdateSummary, @@ -71,17 +71,17 @@ pub(crate) use timeline_event; macro_rules! assert_timeline_stream { // `--- date divider ---` - ( @_ [ $stream:ident ] [ --- date divider --- ; $( $rest:tt )* ] [ $( $accumulator:tt )* ] ) => { + ( @_ [ $iterator:ident ] [ --- date divider --- ; $( $rest:tt )* ] [ $( $accumulator:tt )* ] ) => { assert_timeline_stream!( @_ - [ $stream ] + [ $iterator ] [ $( $rest )* ] [ $( $accumulator )* { assert_matches!( - $stream.next().now_or_never(), - Some(Some(VectorDiff::PushBack { value })) => { + $iterator .next(), + Some(VectorDiff::PushBack { value }) => { assert_matches!( **value, TimelineItemKind::Virtual( @@ -96,17 +96,17 @@ macro_rules! assert_timeline_stream { }; // `append "$event_id"` - ( @_ [ $stream:ident ] [ append $event_id:literal ; $( $rest:tt )* ] [ $( $accumulator:tt )* ] ) => { + ( @_ [ $iterator:ident ] [ append $event_id:literal ; $( $rest:tt )* ] [ $( $accumulator:tt )* ] ) => { assert_timeline_stream!( @_ - [ $stream ] + [ $iterator ] [ $( $rest )* ] [ $( $accumulator )* { assert_matches!( - $stream.next().now_or_never(), - Some(Some(VectorDiff::PushBack { value })) => { + $iterator .next(), + Some(VectorDiff::PushBack { value }) => { assert_matches!( &**value, TimelineItemKind::Event(event_timeline_item) => { @@ -121,17 +121,17 @@ macro_rules! assert_timeline_stream { }; // `prepend --- date divider ---` - ( @_ [ $stream:ident ] [ prepend --- date divider --- ; $( $rest:tt )* ] [ $( $accumulator:tt )* ] ) => { + ( @_ [ $iterator:ident ] [ prepend --- date divider --- ; $( $rest:tt )* ] [ $( $accumulator:tt )* ] ) => { assert_timeline_stream!( @_ - [ $stream ] + [ $iterator ] [ $( $rest )* ] [ $( $accumulator )* { assert_matches!( - $stream.next().now_or_never(), - Some(Some(VectorDiff::PushFront { value })) => { + $iterator .next(), + Some(VectorDiff::PushFront { value }) => { assert_matches!( &**value, TimelineItemKind::Virtual(VirtualTimelineItem::DateDivider(_)) => {} @@ -145,17 +145,17 @@ macro_rules! assert_timeline_stream { // `insert [$nth] "$event_id"` - ( @_ [ $stream:ident ] [ insert [$index:literal] $event_id:literal ; $( $rest:tt )* ] [ $( $accumulator:tt )* ] ) => { + ( @_ [ $iterator:ident ] [ insert [$index:literal] $event_id:literal ; $( $rest:tt )* ] [ $( $accumulator:tt )* ] ) => { assert_timeline_stream!( @_ - [ $stream ] + [ $iterator ] [ $( $rest )* ] [ $( $accumulator )* { assert_matches!( - $stream.next().now_or_never(), - Some(Some(VectorDiff::Insert { index: $index, value })) => { + $iterator .next(), + Some(VectorDiff::Insert { index: $index, value }) => { assert_matches!( &**value, TimelineItemKind::Event(event_timeline_item) => { @@ -170,17 +170,17 @@ macro_rules! assert_timeline_stream { }; // `update [$nth] "$event_id"` - ( @_ [ $stream:ident ] [ update [$index:literal] $event_id:literal ; $( $rest:tt )* ] [ $( $accumulator:tt )* ] ) => { + ( @_ [ $iterator:ident ] [ update [$index:literal] $event_id:literal ; $( $rest:tt )* ] [ $( $accumulator:tt )* ] ) => { assert_timeline_stream!( @_ - [ $stream ] + [ $iterator ] [ $( $rest )* ] [ $( $accumulator )* { assert_matches!( - $stream.next().now_or_never(), - Some(Some(VectorDiff::Set { index: $index, value })) => { + $iterator .next(), + Some(VectorDiff::Set { index: $index, value }) => { assert_matches!( &**value, TimelineItemKind::Event(event_timeline_item) => { @@ -195,17 +195,17 @@ macro_rules! assert_timeline_stream { }; // `remove [$nth]` - ( @_ [ $stream:ident ] [ remove [$index:literal] ; $( $rest:tt )* ] [ $( $accumulator:tt )* ] ) => { + ( @_ [ $iterator:ident ] [ remove [$index:literal] ; $( $rest:tt )* ] [ $( $accumulator:tt )* ] ) => { assert_timeline_stream!( @_ - [ $stream ] + [ $iterator ] [ $( $rest )* ] [ $( $accumulator )* { assert_matches!( - $stream.next().now_or_never(), - Some(Some(VectorDiff::Remove { index: $index })) + $iterator .next(), + Some(VectorDiff::Remove { index: $index }) ); } ] @@ -217,7 +217,13 @@ macro_rules! assert_timeline_stream { }; ( [ $stream:ident ] $( $all:tt )* ) => { - assert_timeline_stream!( @_ [ $stream ] [ $( $all )* ] [] ) + let mut timeline_updates = $stream + .next() + .await + .expect("Failed to poll the stream") + .into_iter(); + + assert_timeline_stream!( @_ [ timeline_updates ] [ $( $all )* ] [] ) }; } @@ -273,7 +279,7 @@ async fn timeline_test_helper( client: &Client, sliding_sync: &SlidingSync, room_id: &RoomId, -) -> Result<(Vector>, impl Stream>>)> { +) -> Result<(Vector>, impl Stream>>>)> { let sliding_sync_room = sliding_sync.get_room(room_id).await.unwrap(); let room_id = sliding_sync_room.room_id(); @@ -283,7 +289,7 @@ async fn timeline_test_helper( let timeline = Timeline::builder(&sdk_room).track_read_marker_and_receipts().build().await?; - Ok(timeline.subscribe().await) + Ok(timeline.subscribe_batched().await) } struct SlidingSyncMatcher; @@ -498,11 +504,12 @@ async fn test_timeline_read_receipts_are_updated_live() -> Result<()> { } }; - assert_let!( - Some(Some(VectorDiff::Set { index: 2, value })) = timeline_stream.next().now_or_never() - ); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 1); - assert_let!(TimelineItemKind::Event(event_timeline_item) = &**value); + assert_let!(VectorDiff::Set { index: 2, value } = &timeline_updates[0]); + + assert_let!(TimelineItemKind::Event(event_timeline_item) = &***value); assert_eq!(event_timeline_item.event_id().unwrap().as_str(), "$x2:bar.org"); let read_receipts = event_timeline_item.read_receipts(); diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/subscribe.rs b/crates/matrix-sdk-ui/tests/integration/timeline/subscribe.rs index 63ae21313..778cba3d9 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/subscribe.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/subscribe.rs @@ -17,7 +17,7 @@ use std::time::Duration; use assert_matches::assert_matches; use assert_matches2::assert_let; use eyeball_im::VectorDiff; -use futures_util::{pin_mut, StreamExt}; +use futures_util::StreamExt; use matrix_sdk::{config::SyncSettings, test_utils::logged_in_client_with_server}; use matrix_sdk_test::{ async_test, event_factory::EventFactory, mocks::mock_encryption_state, sync_timeline_event, @@ -30,7 +30,7 @@ use ruma::{ room_id, user_id, }; use serde_json::json; -use stream_assert::{assert_next_matches, assert_pending}; +use stream_assert::assert_pending; use crate::mock_sync; @@ -93,7 +93,7 @@ async fn test_event_filter() { let room = client.get_room(room_id).unwrap(); let timeline = room.timeline_builder().event_filter(|_, _| true).build().await.unwrap(); - let (_, mut timeline_stream) = timeline.subscribe().await; + let (_, mut timeline_stream) = timeline.subscribe_batched().await; let first_event_id = event_id!("$YTQwYl2ply"); sync_builder.add_joined_room(JoinedRoomBuilder::new(room_id).add_timeline_event( @@ -113,7 +113,10 @@ async fn test_event_filter() { let _response = client.sync_once(sync_settings.clone()).await.unwrap(); server.reset().await; - assert_let!(Some(VectorDiff::PushBack { value: first }) = timeline_stream.next().await); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 2); + + assert_let!(VectorDiff::PushBack { value: first } = &timeline_updates[0]); let first_event = first.as_event().unwrap(); assert_eq!(first_event.event_id(), Some(first_event_id)); assert_eq!(first_event.read_receipts().len(), 1, "implicit read receipt"); @@ -122,7 +125,7 @@ async fn test_event_filter() { assert_matches!(msg.msgtype(), MessageType::Text(_)); assert!(!msg.is_edited()); - assert_let!(Some(VectorDiff::PushFront { value: date_divider }) = timeline_stream.next().await); + assert_let!(VectorDiff::PushFront { value: date_divider } = &timeline_updates[1]); assert!(date_divider.is_date_divider()); let second_event_id = event_id!("$Ga6Y2l0gKY"); @@ -165,18 +168,21 @@ async fn test_event_filter() { let _response = client.sync_once(sync_settings.clone()).await.unwrap(); server.reset().await; - assert_let!(Some(VectorDiff::PushBack { value: second }) = timeline_stream.next().await); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 3); + + assert_let!(VectorDiff::PushBack { value: second } = &timeline_updates[0]); let second_event = second.as_event().unwrap(); assert_eq!(second_event.event_id(), Some(second_event_id)); assert_eq!(second_event.read_receipts().len(), 1, "implicit read receipt"); // The implicit read receipt of Alice is moving from Alice's message... - assert_let!(Some(VectorDiff::Set { index: 1, value: first }) = timeline_stream.next().await); + assert_let!(VectorDiff::Set { index: 1, value: first } = &timeline_updates[1]); assert_eq!(first.as_event().unwrap().read_receipts().len(), 0, "no more implicit read receipt"); // … to Alice's edit. But since this item isn't visible, it's lost in the weeds! // The edit is applied to the first event. - assert_let!(Some(VectorDiff::Set { index: 1, value: first }) = timeline_stream.next().await); + assert_let!(VectorDiff::Set { index: 1, value: first } = &timeline_updates[2]); let first_event = first.as_event().unwrap(); assert!(first_event.read_receipts().is_empty()); assert_matches!(first_event.latest_edit_json(), Some(_)); @@ -184,6 +190,8 @@ async fn test_event_filter() { assert_let!(MessageType::Text(text) = msg.msgtype()); assert_eq!(text.body, "hi"); assert!(msg.is_edited()); + + assert_pending!(timeline_stream); } #[async_test] @@ -203,8 +211,7 @@ async fn test_timeline_is_reset_when_a_user_is_ignored_or_unignored() { let room = client.get_room(room_id).unwrap(); let timeline = room.timeline_builder().build().await.unwrap(); - let (_, timeline_stream) = timeline.subscribe().await; - pin_mut!(timeline_stream); + let (_, mut timeline_stream) = timeline.subscribe_batched().await; let alice = user_id!("@alice:example.org"); let bob = user_id!("@bob:example.org"); @@ -228,21 +235,24 @@ async fn test_timeline_is_reset_when_a_user_is_ignored_or_unignored() { let _response = client.sync_once(sync_settings.clone()).await.unwrap(); server.reset().await; - 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: 0, 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_next_matches!(timeline_stream, VectorDiff::PushFront { value } => { - assert!(value.is_date_divider()); - }); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 5); + + assert_let!(VectorDiff::PushBack { value } = &timeline_updates[0]); + assert_eq!(value.as_event().unwrap().event_id(), Some(first_event_id)); + + assert_let!(VectorDiff::PushBack { value } = &timeline_updates[1]); + assert_eq!(value.as_event().unwrap().event_id(), Some(second_event_id)); + + assert_let!(VectorDiff::Set { index: 0, value } = &timeline_updates[2]); + assert_eq!(value.as_event().unwrap().event_id(), Some(first_event_id)); + + assert_let!(VectorDiff::PushBack { value } = &timeline_updates[3]); + assert_eq!(value.as_event().unwrap().event_id(), Some(third_event_id)); + + assert_let!(VectorDiff::PushFront { value } = &timeline_updates[4]); + assert!(value.is_date_divider()); + assert_pending!(timeline_stream); sync_builder.add_global_account_data_event(GlobalAccountDataTestEvent::Custom(json!({ @@ -258,9 +268,11 @@ async fn test_timeline_is_reset_when_a_user_is_ignored_or_unignored() { let _response = client.sync_once(sync_settings.clone()).await.unwrap(); server.reset().await; + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 1); + // The timeline has been emptied. - assert_next_matches!(timeline_stream, VectorDiff::Clear); - assert_pending!(timeline_stream); + assert_let!(VectorDiff::Clear = &timeline_updates[0]); let fourth_event_id = event_id!("$YTQwYl2pl4"); let fifth_event_id = event_id!("$YTQwYl2pl5"); @@ -278,21 +290,25 @@ async fn test_timeline_is_reset_when_a_user_is_ignored_or_unignored() { let _response = client.sync_once(sync_settings.clone()).await.unwrap(); server.reset().await; + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 5); + // Timeline receives events as before. - assert_next_matches!(timeline_stream, VectorDiff::Clear); // TODO: Remove `RoomEventCacheUpdate::Clear` as it creates double - // `VectorDiff::Clear`. - 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: 0, 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(fifth_event_id)); - }); - assert_next_matches!(timeline_stream, VectorDiff::PushFront { value } => { - assert!(value.is_date_divider()); - }); + assert_let!(VectorDiff::Clear = &timeline_updates[0]); // TODO: Remove `RoomEventCacheUpdate::Clear` as it creates double + // `VectorDiff::Clear`. + + assert_let!(VectorDiff::PushBack { value } = &timeline_updates[1]); + assert_eq!(value.as_event().unwrap().event_id(), Some(fourth_event_id)); + + assert_let!(VectorDiff::Set { index: 0, value } = &timeline_updates[2]); + assert_eq!(value.as_event().unwrap().event_id(), Some(fourth_event_id)); + + assert_let!(VectorDiff::PushBack { value } = &timeline_updates[3]); + assert_eq!(value.as_event().unwrap().event_id(), Some(fifth_event_id)); + + assert_let!(VectorDiff::PushFront { value } = &timeline_updates[4]); + assert!(value.is_date_divider()); + assert_pending!(timeline_stream); } @@ -313,8 +329,7 @@ async fn test_profile_updates() { let room = client.get_room(room_id).unwrap(); let timeline = room.timeline_builder().build().await.unwrap(); - let (_, timeline_stream) = timeline.subscribe().await; - pin_mut!(timeline_stream); + let (_, mut timeline_stream) = timeline.subscribe_batched().await; let alice = "@alice:example.org"; let bob = "@bob:example.org"; @@ -351,19 +366,21 @@ async fn test_profile_updates() { let _response = client.sync_once(sync_settings.clone()).await.unwrap(); server.reset().await; - let item_1 = assert_next_matches!(timeline_stream, VectorDiff::PushBack { value } => value); + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 3); + + assert_let!(VectorDiff::PushBack { value: item_1 } = &timeline_updates[0]); let event_1_item = item_1.as_event().unwrap(); assert_eq!(event_1_item.event_id(), Some(event_1_id)); assert_matches!(event_1_item.sender_profile(), TimelineDetails::Unavailable); - let item_2 = assert_next_matches!(timeline_stream, VectorDiff::PushBack { value } => value); + assert_let!(VectorDiff::PushBack { value: item_2 } = &timeline_updates[1]); let event_2_item = item_2.as_event().unwrap(); assert_eq!(event_2_item.event_id(), Some(event_2_id)); assert_matches!(event_2_item.sender_profile(), TimelineDetails::Unavailable); - assert_next_matches!(timeline_stream, VectorDiff::PushFront { value } => { - assert!(value.is_date_divider()); - }); + assert_let!(VectorDiff::PushFront { value } = &timeline_updates[2]); + assert!(value.is_date_divider()); assert_pending!(timeline_stream); @@ -412,13 +429,15 @@ async fn test_profile_updates() { let _response = client.sync_once(sync_settings.clone()).await.unwrap(); server.reset().await; + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 8); + // Read receipt change. - assert_next_matches!(timeline_stream, VectorDiff::Set { index: 2, value } => { - assert_eq!(value.as_event().unwrap().event_id(), Some(event_2_id)); - }); + assert_let!(VectorDiff::Set { index: 2, value } = &timeline_updates[0]); + assert_eq!(value.as_event().unwrap().event_id(), Some(event_2_id)); // The events are added. - let item_3 = assert_next_matches!(timeline_stream, VectorDiff::PushBack { value } => value); + assert_let!(VectorDiff::PushBack { value: item_3 } = &timeline_updates[1]); let event_3_item = item_3.as_event().unwrap(); assert_eq!(event_3_item.event_id(), Some(event_3_id)); let profile = @@ -427,11 +446,10 @@ async fn test_profile_updates() { assert!(!profile.display_name_ambiguous); // Read receipt change. - assert_next_matches!(timeline_stream, VectorDiff::Set { index: 1, value } => { - assert_eq!(value.as_event().unwrap().event_id(), Some(event_1_id)); - }); + assert_let!(VectorDiff::Set { index: 1, value } = &timeline_updates[2]); + assert_eq!(value.as_event().unwrap().event_id(), Some(event_1_id)); - let item_4 = assert_next_matches!(timeline_stream, VectorDiff::PushBack { value } => value); + assert_let!(VectorDiff::PushBack { value: item_4 } = &timeline_updates[3]); let event_4_item = item_4.as_event().unwrap(); assert_eq!(event_4_item.event_id(), Some(event_4_id)); let profile = @@ -440,11 +458,10 @@ async fn test_profile_updates() { assert!(!profile.display_name_ambiguous); // Read receipt change. - assert_next_matches!(timeline_stream, VectorDiff::Set { index: 4, value } => { - assert_eq!(value.as_event().unwrap().event_id(), Some(event_4_id)); - }); + assert_let!(VectorDiff::Set { index: 4, value } = &timeline_updates[4]); + assert_eq!(value.as_event().unwrap().event_id(), Some(event_4_id)); - let item_5 = assert_next_matches!(timeline_stream, VectorDiff::PushBack { value } => value); + assert_let!(VectorDiff::PushBack { value: item_5 } = &timeline_updates[5]); let event_5_item = item_5.as_event().unwrap(); assert_eq!(event_5_item.event_id(), Some(event_5_id)); let profile = @@ -453,8 +470,7 @@ async fn test_profile_updates() { assert!(!profile.display_name_ambiguous); // The profiles changed. - let item_1 = - assert_next_matches!(timeline_stream, VectorDiff::Set { index: 1, value } => value); + assert_let!(VectorDiff::Set { index: 1, value: item_1 } = &timeline_updates[6]); let event_1_item = item_1.as_event().unwrap(); assert_eq!(event_1_item.event_id(), Some(event_1_id)); let profile = @@ -462,8 +478,7 @@ async fn test_profile_updates() { assert_eq!(profile.display_name.as_deref(), Some("Alice")); assert!(!profile.display_name_ambiguous); - let item_2 = - assert_next_matches!(timeline_stream, VectorDiff::Set { index: 2, value } => value); + assert_let!(VectorDiff::Set { index: 2, value: item_2 } = &timeline_updates[7]); let event_2_item = item_2.as_event().unwrap(); assert_eq!(event_2_item.event_id(), Some(event_2_id)); let profile = @@ -471,8 +486,6 @@ async fn test_profile_updates() { assert_eq!(profile.display_name.as_deref(), Some("Member")); assert!(!profile.display_name_ambiguous); - assert_pending!(timeline_stream); - // Change name to be ambiguous. let event_6_id = event_id!("$YTQwYl2pl6"); @@ -494,13 +507,15 @@ async fn test_profile_updates() { let _response = client.sync_once(sync_settings.clone()).await.unwrap(); server.reset().await; + assert_let!(Some(timeline_updates) = timeline_stream.next().await); + assert_eq!(timeline_updates.len(), 7); + // Read receipt change. - assert_next_matches!(timeline_stream, VectorDiff::Set { index: 5, value } => { - assert_eq!(value.as_event().unwrap().event_id(), Some(event_5_id)); - }); + assert_let!(VectorDiff::Set { index: 5, value } = &timeline_updates[0]); + assert_eq!(value.as_event().unwrap().event_id(), Some(event_5_id)); // The event is added. - let item_6 = assert_next_matches!(timeline_stream, VectorDiff::PushBack { value } => value); + assert_let!(VectorDiff::PushBack { value: item_6 } = &timeline_updates[1]); let event_6_item = item_6.as_event().unwrap(); assert_eq!(event_6_item.event_id(), Some(event_6_id)); let profile = @@ -509,8 +524,7 @@ async fn test_profile_updates() { assert!(profile.display_name_ambiguous); // The profiles changed. - let item_1 = - assert_next_matches!(timeline_stream, VectorDiff::Set { index: 1, value } => value); + assert_let!(VectorDiff::Set { index: 1, value: item_1 } = &timeline_updates[2]); let event_1_item = item_1.as_event().unwrap(); assert_eq!(event_1_item.event_id(), Some(event_1_id)); let profile = @@ -518,8 +532,7 @@ async fn test_profile_updates() { assert_eq!(profile.display_name.as_deref(), Some("Member")); assert!(profile.display_name_ambiguous); - let item_2 = - assert_next_matches!(timeline_stream, VectorDiff::Set { index: 2, value } => value); + assert_let!(VectorDiff::Set { index: 2, value: item_2 } = &timeline_updates[3]); let event_2_item = item_2.as_event().unwrap(); assert_eq!(event_2_item.event_id(), Some(event_2_id)); let profile = @@ -527,8 +540,7 @@ async fn test_profile_updates() { assert_eq!(profile.display_name.as_deref(), Some("Member")); assert!(profile.display_name_ambiguous); - let item_3 = - assert_next_matches!(timeline_stream, VectorDiff::Set { index: 3, value } => value); + assert_let!(VectorDiff::Set { index: 3, value: item_3 } = &timeline_updates[4]); let event_3_item = item_3.as_event().unwrap(); assert_eq!(event_3_item.event_id(), Some(event_3_id)); let profile = @@ -536,8 +548,7 @@ async fn test_profile_updates() { assert_eq!(profile.display_name.as_deref(), Some("Member")); assert!(profile.display_name_ambiguous); - let item_4 = - assert_next_matches!(timeline_stream, VectorDiff::Set { index: 4, value } => value); + assert_let!(VectorDiff::Set { index: 4, value: item_4 } = &timeline_updates[5]); let event_4_item = item_4.as_event().unwrap(); assert_eq!(event_4_item.event_id(), Some(event_4_id)); let profile = @@ -545,8 +556,7 @@ async fn test_profile_updates() { assert_eq!(profile.display_name.as_deref(), Some("Member")); assert!(profile.display_name_ambiguous); - let item_5 = - assert_next_matches!(timeline_stream, VectorDiff::Set { index: 5, value } => value); + assert_let!(VectorDiff::Set { index: 5, value: item_5 } = &timeline_updates[6]); let event_5_item = item_5.as_event().unwrap(); assert_eq!(event_5_item.event_id(), Some(event_5_id)); let profile = diff --git a/examples/timeline/src/main.rs b/examples/timeline/src/main.rs index b36a005a7..cf5c1157e 100644 --- a/examples/timeline/src/main.rs +++ b/examples/timeline/src/main.rs @@ -71,12 +71,12 @@ async fn main() -> Result<()> { // Get the timeline stream and listen to it. let room = client.get_room(&room_id).unwrap(); let timeline = room.timeline().await.unwrap(); - let (timeline_items, mut timeline_stream) = timeline.subscribe().await; + let (timeline_items, mut timeline_stream) = timeline.subscribe_batched().await; println!("Initial timeline items: {timeline_items:#?}"); tokio::spawn(async move { - while let Some(diff) = timeline_stream.next().await { - println!("Received a timeline diff: {diff:#?}"); + while let Some(diffs) = timeline_stream.next().await { + println!("Received timeline diffs: {diffs:#?}"); } }); diff --git a/labs/multiverse/src/main.rs b/labs/multiverse/src/main.rs index 9da33ecf5..e1d76ce4f 100644 --- a/labs/multiverse/src/main.rs +++ b/labs/multiverse/src/main.rs @@ -266,7 +266,7 @@ impl App { // Save the timeline in the cache. let sdk_timeline = ui_room.timeline().unwrap(); - let (items, stream) = sdk_timeline.subscribe().await; + let (items, stream) = sdk_timeline.subscribe_batched().await; let items = Arc::new(Mutex::new(items)); // Spawn a timeline task that will listen to all the timeline item changes. @@ -274,9 +274,12 @@ impl App { let timeline_task = spawn(async move { pin_mut!(stream); let items = i; - while let Some(diff) = stream.next().await { + while let Some(diffs) = stream.next().await { let mut items = items.lock().unwrap(); - diff.apply(&mut items); + + for diff in diffs { + diff.apply(&mut items); + } } }); diff --git a/testing/matrix-sdk-integration-testing/src/tests/sliding_sync/room.rs b/testing/matrix-sdk-integration-testing/src/tests/sliding_sync/room.rs index 7a441e9ec..0bfd46359 100644 --- a/testing/matrix-sdk-integration-testing/src/tests/sliding_sync/room.rs +++ b/testing/matrix-sdk-integration-testing/src/tests/sliding_sync/room.rs @@ -893,7 +893,7 @@ async fn test_delayed_invite_response_and_sent_message_decryption() { // Join the room from Bob's client. let bob_timeline = bob_room.timeline().await.unwrap(); - let (_, timeline_stream) = bob_timeline.subscribe().await; + let (_, timeline_stream) = bob_timeline.subscribe_batched().await; pin_mut!(timeline_stream); info!("Bob joins the room."); @@ -908,31 +908,33 @@ async fn test_delayed_invite_response_and_sent_message_decryption() { bob_timeline.paginate_backwards(3).await.unwrap(); // Look for the sent message, which should not be an UTD event. - while let Ok(Some(diff)) = timeout(Duration::from_secs(3), timeline_stream.next()).await { - trace!(?diff, "Received diff from Bob's room"); + while let Ok(Some(diffs)) = timeout(Duration::from_secs(3), timeline_stream.next()).await { + trace!(?diffs, "Received diffs from Bob's room"); - match diff { - VectorDiff::PushFront { value: event } - | VectorDiff::PushBack { value: event } - | VectorDiff::Insert { value: event, .. } - | VectorDiff::Set { value: event, .. } => { - let Some(event) = event.as_event() else { - continue; - }; + for diff in diffs { + match diff { + VectorDiff::PushFront { value: event } + | VectorDiff::PushBack { value: event } + | VectorDiff::Insert { value: event, .. } + | VectorDiff::Set { value: event, .. } => { + let Some(event) = event.as_event() else { + continue; + }; - let content = event.content(); + let content = event.content(); - if content.as_unable_to_decrypt().is_some() { - info!("Observed UTD for {}", event.event_id().unwrap()); + if content.as_unable_to_decrypt().is_some() { + info!("Observed UTD for {}", event.event_id().unwrap()); + } + + if let Some(message) = content.as_message() { + assert_eq!(message.body(), "hello world"); + return; + } } - if let Some(message) = content.as_message() { - assert_eq!(message.body(), "hello world"); - return; - } + _ => {} } - - _ => {} } } diff --git a/testing/matrix-sdk-integration-testing/src/tests/timeline.rs b/testing/matrix-sdk-integration-testing/src/tests/timeline.rs index 40dbe1b8e..8f8c81ce8 100644 --- a/testing/matrix-sdk-integration-testing/src/tests/timeline.rs +++ b/testing/matrix-sdk-integration-testing/src/tests/timeline.rs @@ -55,12 +55,9 @@ use crate::helpers::TestClientBuilder; /// /// A macro to help lowering compile times and getting better error locations. macro_rules! assert_event_is_updated { - ($stream:expr, $event_id:expr, $index:expr) => {{ - assert_let!( - Ok(Some(VectorDiff::Set { index: i, value: event })) = - timeout(Duration::from_secs(1), $stream.next()).await - ); - assert_eq!(i, $index, "unexpected position for event update, value = {event:?}"); + ($diff:expr, $event_id:expr, $index:expr) => {{ + assert_let!(VectorDiff::Set { index: i, value: event } = &$diff); + assert_eq!(*i, $index, "unexpected position for event update, value = {event:?}"); let event = event.as_event().unwrap(); assert_eq!(event.event_id().unwrap(), $event_id); @@ -94,7 +91,7 @@ async fn test_toggling_reaction() -> Result<()> { // waiting for it. let timeline = room.timeline().await.unwrap(); - let (mut items, mut stream) = timeline.subscribe().await; + let (mut items, mut stream) = timeline.subscribe_batched().await; let event_id_task: JoinHandle> = spawn(async move { let find_event_id = |items: &Vector>| { @@ -114,9 +111,13 @@ async fn test_toggling_reaction() -> Result<()> { warn!(?items, "Waiting for updates…"); - while let Some(diff) = stream.next().await { - warn!(?diff, "received a diff"); - diff.apply(&mut items); + while let Some(diffs) = stream.next().await { + warn!(?diffs, "received diffs"); + + for diff in diffs { + diff.apply(&mut items); + } + if let Some(event_id) = find_event_id(&items) { return Ok(event_id); } @@ -145,12 +146,14 @@ async fn test_toggling_reaction() -> Result<()> { // Give a bit of time for the timeline to process all sync updates. sleep(Duration::from_secs(1)).await; - let (mut items, mut stream) = timeline.subscribe().await; + let (mut items, mut stream) = timeline.subscribe_batched().await; // Skip all stream updates that have happened so far. debug!("Skipping all other stream updates…"); - while let Some(Some(diff)) = stream.next().now_or_never() { - diff.apply(&mut items); + while let Some(Some(diffs)) = stream.next().now_or_never() { + for diff in diffs { + diff.apply(&mut items); + } } let (message_position, item_id) = items @@ -171,9 +174,14 @@ async fn test_toggling_reaction() -> Result<()> { // Add the reaction. timeline.toggle_reaction(&item_id, &reaction_key).await.expect("toggling reaction"); + sleep(Duration::from_secs(1)).await; + + assert_let!(Some(timeline_updates) = stream.next().await); + assert_eq!(timeline_updates.len(), 2); + // Local echo is added. { - let event = assert_event_is_updated!(stream, event_id, message_position); + let event = assert_event_is_updated!(timeline_updates[0], event_id, message_position); let reactions = event.reactions().get(&reaction_key).unwrap(); let reaction = reactions.get(&user_id).unwrap(); assert_matches!(reaction.status, ReactionStatus::LocalToRemote(..)); @@ -181,7 +189,7 @@ async fn test_toggling_reaction() -> Result<()> { // Remote echo is added. { - let event = assert_event_is_updated!(stream, event_id, message_position); + let event = assert_event_is_updated!(timeline_updates[1], event_id, message_position); let reactions = event.reactions().get(&reaction_key).unwrap(); assert_eq!(reactions.keys().count(), 1); @@ -202,11 +210,16 @@ async fn test_toggling_reaction() -> Result<()> { .await .expect("toggling reaction the second time"); + sleep(Duration::from_secs(1)).await; + + assert_let!(Some(timeline_updates) = stream.next().await); + assert_eq!(timeline_updates.len(), 1); + // The reaction is removed. - let event = assert_event_is_updated!(stream, event_id, message_position); + let event = assert_event_is_updated!(timeline_updates[0], event_id, message_position); assert!(event.reactions().is_empty()); - assert!(stream.next().now_or_never().is_none()); + assert_pending!(stream); } Ok(()) From 1d83d42e9fd77f1aed4acb7e3c7d8879ba54ba91 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 21 Jan 2025 13:54:14 +0100 Subject: [PATCH 03/55] chore(ui): Remove `Timeline::subscribe`. This patch removes `Timeline::subscribe`. There is `Timeline::subscribe_batched`, which is the only useful API. `subscribe` was only used by our tests, and `matrix-sdk-ffi` uses `subscribe_batched` only. --- .../src/timeline/controller/mod.rs | 5 +++-- crates/matrix-sdk-ui/src/timeline/mod.rs | 17 ++--------------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/crates/matrix-sdk-ui/src/timeline/controller/mod.rs b/crates/matrix-sdk-ui/src/timeline/controller/mod.rs index 64048aeda..75c7f8983 100644 --- a/crates/matrix-sdk-ui/src/timeline/controller/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/controller/mod.rs @@ -20,14 +20,14 @@ use eyeball_im_util::vector::VectorObserverExt; use futures_core::Stream; use imbl::Vector; #[cfg(test)] -use matrix_sdk::crypto::OlmMachine; +use matrix_sdk::{crypto::OlmMachine, SendOutsideWasm}; use matrix_sdk::{ deserialized_responses::{SyncTimelineEvent, TimelineEventKind as SdkTimelineEventKind}, event_cache::{paginator::Paginator, RoomEventCache}, send_queue::{ LocalEcho, LocalEchoContent, RoomSendQueueUpdate, SendHandle, SendReactionHandle, }, - Result, Room, SendOutsideWasm, + Result, Room, }; use ruma::{ api::client::receipt::create_receipt::v3::ReceiptType as SendReceiptType, @@ -477,6 +477,7 @@ impl TimelineController

{ self.state.read().await.items.clone_items() } + #[cfg(test)] pub(super) async fn subscribe( &self, ) -> ( diff --git a/crates/matrix-sdk-ui/src/timeline/mod.rs b/crates/matrix-sdk-ui/src/timeline/mod.rs index 721992527..21d7101f4 100644 --- a/crates/matrix-sdk-ui/src/timeline/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/mod.rs @@ -266,23 +266,10 @@ impl Timeline { } } - /// Get the current timeline items, and a stream of changes. - /// - /// You can poll this stream to receive updates. See - /// [`futures_util::StreamExt`] for a high-level API on top of [`Stream`]. - pub async fn subscribe( - &self, - ) -> (Vector>, impl Stream>>) { - let (items, stream) = self.controller.subscribe().await; - let stream = TimelineStream::new(stream, self.drop_handle.clone()); - (items, stream) - } - /// Get the current timeline items, and a batched stream of changes. /// - /// In contrast to [`subscribe`](Self::subscribe), this stream can yield - /// multiple diffs at once. The batching is done such that no arbitrary - /// delays are added. + /// This stream can yield multiple diffs at once. The batching is done such + /// that no arbitrary delays are added. pub async fn subscribe_batched( &self, ) -> (Vector>, impl Stream>>>) { From a528624274ecd557427835511eb24513f00696d3 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Wed, 22 Jan 2025 13:56:53 +0200 Subject: [PATCH 04/55] chore(ffi): replace all the different timeline builder methods with one taking a configuration (#4561) --- bindings/matrix-sdk-ffi/src/error.rs | 8 +- bindings/matrix-sdk-ffi/src/room.rs | 137 ++++-------------- .../src/timeline/configuration.rs | 85 +++++++++++ bindings/matrix-sdk-ffi/src/timeline/mod.rs | 18 +-- 4 files changed, 125 insertions(+), 123 deletions(-) create mode 100644 bindings/matrix-sdk-ffi/src/timeline/configuration.rs diff --git a/bindings/matrix-sdk-ffi/src/error.rs b/bindings/matrix-sdk-ffi/src/error.rs index 5ce1bf48e..9f630a767 100644 --- a/bindings/matrix-sdk-ffi/src/error.rs +++ b/bindings/matrix-sdk-ffi/src/error.rs @@ -9,7 +9,7 @@ use matrix_sdk::{ use matrix_sdk_ui::{encryption_sync_service, notification_client, sync_service, timeline}; use uniffi::UnexpectedUniFFICallbackError; -use crate::room_list::RoomListError; +use crate::{room_list::RoomListError, timeline::FocusEventError}; #[derive(Debug, thiserror::Error)] pub enum ClientError { @@ -161,6 +161,12 @@ impl From for ClientError { } } +impl From for ClientError { + fn from(e: FocusEventError) -> Self { + Self::new(e) + } +} + /// Bindings version of the sdk type replacing OwnedUserId/DeviceIds with simple /// String. /// diff --git a/bindings/matrix-sdk-ffi/src/room.rs b/bindings/matrix-sdk-ffi/src/room.rs index 62f7b6780..08e9fdbee 100644 --- a/bindings/matrix-sdk-ffi/src/room.rs +++ b/bindings/matrix-sdk-ffi/src/room.rs @@ -4,14 +4,13 @@ use anyhow::{Context, Result}; use futures_util::{pin_mut, StreamExt}; use matrix_sdk::{ crypto::LocalTrust, - event_cache::paginator::PaginatorError, room::{ edit::EditedContent, power_levels::RoomPowerLevelChanges, Room as SdkRoom, RoomMemberRole, }, ComposerDraft as SdkComposerDraft, ComposerDraftType as SdkComposerDraftType, RoomHero as SdkRoomHero, RoomMemberships, RoomState, }; -use matrix_sdk_ui::timeline::{default_event_filter, PaginationError, RoomExt, TimelineFocus}; +use matrix_sdk_ui::timeline::{default_event_filter, RoomExt}; use mime::Mime; use ruma::{ api::client::room::report_content, @@ -36,12 +35,15 @@ use crate::{ chunk_iterator::ChunkIterator, client::{JoinRule, RoomVisibility}, error::{ClientError, MediaInfoError, NotYetImplemented, RoomError}, - event::{MessageLikeEventType, RoomMessageEventMessageType, StateEventType}, + event::{MessageLikeEventType, StateEventType}, identity_status_change::IdentityStatusChange, room_info::RoomInfo, room_member::RoomMember, ruma::{ImageInfo, Mentions, NotifyType}, - timeline::{DateDividerMode, FocusEventError, ReceiptType, SendHandle, Timeline}, + timeline::{ + configuration::{AllowedMessageTypes, TimelineConfiguration}, + ReceiptType, SendHandle, Timeline, + }, utils::u64_to_uint, TaskHandle, }; @@ -87,10 +89,6 @@ impl Room { #[matrix_sdk_ffi_macros::export] impl Room { - pub fn id(&self) -> String { - self.inner.room_id().to_string() - } - /// Returns the room's name from the state event if available, otherwise /// compute a room name based on the room's nature (DM or not) and number of /// members. @@ -200,115 +198,44 @@ impl Room { } } - /// Returns a timeline focused on the given event. - /// - /// Note: this timeline is independent from that returned with - /// [`Self::timeline`], and as such it is not cached. - pub async fn timeline_focused_on_event( + /// Build a new timeline instance with the given configuration. + pub async fn timeline_with_configuration( &self, - event_id: String, - num_context_events: u16, - internal_id_prefix: Option, - ) -> Result, FocusEventError> { - let parsed_event_id = EventId::parse(&event_id).map_err(|err| { - FocusEventError::InvalidEventId { event_id: event_id.clone(), err: err.to_string() } - })?; - - let room = &self.inner; - - let mut builder = matrix_sdk_ui::timeline::Timeline::builder(room); - - if let Some(internal_id_prefix) = internal_id_prefix { - builder = builder.with_internal_id_prefix(internal_id_prefix); - } - - let timeline = match builder - .with_focus(TimelineFocus::Event { target: parsed_event_id, num_context_events }) - .build() - .await - { - Ok(t) => t, - Err(err) => { - if let matrix_sdk_ui::timeline::Error::PaginationError( - PaginationError::Paginator(PaginatorError::EventNotFound(..)), - ) = err - { - return Err(FocusEventError::EventNotFound { event_id: event_id.to_string() }); - } - return Err(FocusEventError::Other { msg: err.to_string() }); - } - }; - - Ok(Timeline::new(timeline)) - } - - pub async fn pinned_events_timeline( - &self, - internal_id_prefix: Option, - max_events_to_load: u16, - max_concurrent_requests: u16, - ) -> Result, ClientError> { - let room = &self.inner; - - let mut builder = matrix_sdk_ui::timeline::Timeline::builder(room); - - if let Some(internal_id_prefix) = internal_id_prefix { - builder = builder.with_internal_id_prefix(internal_id_prefix); - } - - let timeline = builder - .with_focus(TimelineFocus::PinnedEvents { max_events_to_load, max_concurrent_requests }) - .build() - .await?; - - Ok(Timeline::new(timeline)) - } - - /// A timeline instance that can be configured to only include RoomMessage - /// type events and filter those further based on their message type. - /// - /// Virtual timeline items will still be provided and the - /// `default_event_filter` will be applied before everything else. - /// - /// # Arguments - /// - /// * `internal_id_prefix` - An optional String that will be prepended to - /// all the timeline item's internal IDs, making it possible to - /// distinguish different timeline instances from each other. - /// - /// * `allowed_message_types` - A list of `RoomMessageEventMessageType` that - /// will be allowed to appear in the timeline - pub async fn message_filtered_timeline( - &self, - internal_id_prefix: Option, - allowed_message_types: Vec, - date_divider_mode: DateDividerMode, + configuration: TimelineConfiguration, ) -> Result, ClientError> { let mut builder = matrix_sdk_ui::timeline::Timeline::builder(&self.inner); - if let Some(internal_id_prefix) = internal_id_prefix { + builder = builder.with_focus(configuration.focus.try_into()?); + + if let AllowedMessageTypes::Only { types } = configuration.allowed_message_types { + builder = builder.event_filter(move |event, room_version_id| { + default_event_filter(event, room_version_id) + && match event { + AnySyncTimelineEvent::MessageLike(msg) => match msg.original_content() { + Some(AnyMessageLikeEventContent::RoomMessage(content)) => { + types.contains(&content.msgtype.into()) + } + _ => false, + }, + _ => false, + } + }); + } + + if let Some(internal_id_prefix) = configuration.internal_id_prefix { builder = builder.with_internal_id_prefix(internal_id_prefix); } - builder = builder.with_date_divider_mode(date_divider_mode.into()); - - builder = builder.event_filter(move |event, room_version_id| { - default_event_filter(event, room_version_id) - && match event { - AnySyncTimelineEvent::MessageLike(msg) => match msg.original_content() { - Some(AnyMessageLikeEventContent::RoomMessage(content)) => { - allowed_message_types.contains(&content.msgtype.into()) - } - _ => false, - }, - _ => false, - } - }); + builder = builder.with_date_divider_mode(configuration.date_divider_mode.into()); let timeline = builder.build().await?; Ok(Timeline::new(timeline)) } + pub fn id(&self) -> String { + self.inner.room_id().to_string() + } + pub fn is_encrypted(&self) -> Result { Ok(RUNTIME.block_on(self.inner.is_encrypted())?) } diff --git a/bindings/matrix-sdk-ffi/src/timeline/configuration.rs b/bindings/matrix-sdk-ffi/src/timeline/configuration.rs new file mode 100644 index 000000000..9b20f99e9 --- /dev/null +++ b/bindings/matrix-sdk-ffi/src/timeline/configuration.rs @@ -0,0 +1,85 @@ +use ruma::EventId; + +use super::FocusEventError; +use crate::{error::ClientError, event::RoomMessageEventMessageType}; + +#[derive(uniffi::Enum)] +pub enum TimelineFocus { + Live, + Event { event_id: String, num_context_events: u16 }, + PinnedEvents { max_events_to_load: u16, max_concurrent_requests: u16 }, +} + +impl TryFrom for matrix_sdk_ui::timeline::TimelineFocus { + type Error = ClientError; + + fn try_from( + value: TimelineFocus, + ) -> Result { + match value { + TimelineFocus::Live => Ok(Self::Live), + TimelineFocus::Event { event_id, num_context_events } => { + let parsed_event_id = + EventId::parse(&event_id).map_err(|err| FocusEventError::InvalidEventId { + event_id: event_id.clone(), + err: err.to_string(), + })?; + + Ok(Self::Event { target: parsed_event_id, num_context_events }) + } + TimelineFocus::PinnedEvents { max_events_to_load, max_concurrent_requests } => { + Ok(Self::PinnedEvents { max_events_to_load, max_concurrent_requests }) + } + } + } +} + +/// Changes how date dividers get inserted, either in between each day or in +/// between each month +#[derive(uniffi::Enum)] +pub enum DateDividerMode { + Daily, + Monthly, +} + +impl From for matrix_sdk_ui::timeline::DateDividerMode { + fn from(value: DateDividerMode) -> Self { + match value { + DateDividerMode::Daily => Self::Daily, + DateDividerMode::Monthly => Self::Monthly, + } + } +} + +#[derive(uniffi::Enum)] +pub enum AllowedMessageTypes { + All, + Only { types: Vec }, +} + +/// Various options used to configure the timeline's behavior. +/// +/// # Arguments +/// +/// * `internal_id_prefix` - +/// +/// * `allowed_message_types` - +/// +/// * `date_divider_mode` - +#[derive(uniffi::Record)] +pub struct TimelineConfiguration { + /// What should the timeline focus on? + pub focus: TimelineFocus, + + /// A list of [`RoomMessageEventMessageType`] that will be allowed to appear + /// in the timeline + pub allowed_message_types: AllowedMessageTypes, + + /// An optional String that will be prepended to + /// all the timeline item's internal IDs, making it possible to + /// distinguish different timeline instances from each other. + pub internal_id_prefix: Option, + + /// How often to insert date dividers + pub date_divider_mode: DateDividerMode, +} diff --git a/bindings/matrix-sdk-ffi/src/timeline/mod.rs b/bindings/matrix-sdk-ffi/src/timeline/mod.rs index 3520ce64c..3635e81ce 100644 --- a/bindings/matrix-sdk-ffi/src/timeline/mod.rs +++ b/bindings/matrix-sdk-ffi/src/timeline/mod.rs @@ -79,6 +79,7 @@ use crate::{ RUNTIME, }; +pub mod configuration; mod content; pub use content::MessageContent; @@ -1322,20 +1323,3 @@ impl LazyTimelineItemProvider { self.0.local_echo_send_handle().map(|handle| Arc::new(SendHandle::new(handle))) } } - -/// Changes how date dividers get inserted, either in between each day or in -/// between each month -#[derive(Debug, Clone, uniffi::Enum)] -pub enum DateDividerMode { - Daily, - Monthly, -} - -impl From for matrix_sdk_ui::timeline::DateDividerMode { - fn from(value: DateDividerMode) -> Self { - match value { - DateDividerMode::Daily => Self::Daily, - DateDividerMode::Monthly => Self::Monthly, - } - } -} From 02c2e5585553056e10d960fb0c26188d350b6170 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Wed, 22 Jan 2025 15:54:01 +0100 Subject: [PATCH 05/55] refactor(sdk): Move matrix_auth module to authentication::matrix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Kévin Commaille --- benchmarks/benches/store_bench.rs | 2 +- bindings/matrix-sdk-ffi/src/client.rs | 11 ++++++---- .../src/room_list_service/mod.rs | 2 +- .../integration/encryption_sync_service.rs | 2 +- .../matrix}/login_builder.rs | 0 .../matrix}/mod.rs | 8 ++++---- crates/matrix-sdk/src/authentication/mod.rs | 20 +++++++++---------- crates/matrix-sdk/src/client/mod.rs | 5 +++-- crates/matrix-sdk/src/docs/encryption.md | 2 +- crates/matrix-sdk/src/encryption/mod.rs | 2 +- crates/matrix-sdk/src/lib.rs | 1 - crates/matrix-sdk/src/room/mod.rs | 2 +- crates/matrix-sdk/src/sliding_sync/client.rs | 2 +- crates/matrix-sdk/src/test_utils/client.rs | 2 +- crates/matrix-sdk/src/test_utils/mod.rs | 2 +- crates/matrix-sdk/tests/integration/client.rs | 2 +- .../tests/integration/encryption/backups.rs | 2 +- .../integration/encryption/cross_signing.rs | 2 +- .../tests/integration/encryption/recovery.rs | 2 +- .../integration/encryption/secret_storage.rs | 2 +- .../integration/encryption/verification.rs | 2 +- .../tests/integration/matrix_auth.rs | 2 +- crates/matrix-sdk/tests/integration/media.rs | 2 +- .../tests/integration/refresh_token.rs | 2 +- examples/persist_session/src/main.rs | 2 +- examples/secret_storage/src/main.rs | 2 +- labs/multiverse/src/main.rs | 2 +- 27 files changed, 45 insertions(+), 42 deletions(-) rename crates/matrix-sdk/src/{matrix_auth => authentication/matrix}/login_builder.rs (100%) rename crates/matrix-sdk/src/{matrix_auth => authentication/matrix}/mod.rs (99%) diff --git a/benchmarks/benches/store_bench.rs b/benchmarks/benches/store_bench.rs index bc6cd8f28..9ec0b115f 100644 --- a/benchmarks/benches/store_bench.rs +++ b/benchmarks/benches/store_bench.rs @@ -2,8 +2,8 @@ use std::sync::Arc; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; use matrix_sdk::{ + authentication::matrix::{MatrixSession, MatrixSessionTokens}, config::StoreConfig, - matrix_auth::{MatrixSession, MatrixSessionTokens}, Client, RoomInfo, RoomState, StateChanges, }; use matrix_sdk_base::{store::MemoryStore, SessionMeta, StateStore as _}; diff --git a/bindings/matrix-sdk-ffi/src/client.rs b/bindings/matrix-sdk-ffi/src/client.rs index af8ddb438..66d2f0256 100644 --- a/bindings/matrix-sdk-ffi/src/client.rs +++ b/bindings/matrix-sdk-ffi/src/client.rs @@ -1535,10 +1535,13 @@ impl Session { match auth_api { // Build the session from the regular Matrix Auth Session. AuthApi::Matrix(a) => { - let matrix_sdk::matrix_auth::MatrixSession { + let matrix_sdk::authentication::matrix::MatrixSession { meta: matrix_sdk::SessionMeta { user_id, device_id }, tokens: - matrix_sdk::matrix_auth::MatrixSessionTokens { access_token, refresh_token }, + matrix_sdk::authentication::matrix::MatrixSessionTokens { + access_token, + refresh_token, + }, } = a.session().context("Missing session")?; Ok(Session { @@ -1639,12 +1642,12 @@ impl TryFrom for AuthSession { Ok(AuthSession::Oidc(session.into())) } else { // Create a regular Matrix Session. - let session = matrix_sdk::matrix_auth::MatrixSession { + let session = matrix_sdk::authentication::matrix::MatrixSession { meta: matrix_sdk::SessionMeta { user_id: user_id.try_into()?, device_id: device_id.into(), }, - tokens: matrix_sdk::matrix_auth::MatrixSessionTokens { + tokens: matrix_sdk::authentication::matrix::MatrixSessionTokens { access_token, refresh_token, }, diff --git a/crates/matrix-sdk-ui/src/room_list_service/mod.rs b/crates/matrix-sdk-ui/src/room_list_service/mod.rs index 37a36145c..34246bcc2 100644 --- a/crates/matrix-sdk-ui/src/room_list_service/mod.rs +++ b/crates/matrix-sdk-ui/src/room_list_service/mod.rs @@ -470,8 +470,8 @@ mod tests { use assert_matches::assert_matches; use futures_util::{pin_mut, StreamExt}; use matrix_sdk::{ + authentication::matrix::{MatrixSession, MatrixSessionTokens}, config::RequestConfig, - matrix_auth::{MatrixSession, MatrixSessionTokens}, reqwest::Url, sliding_sync::Version as SlidingSyncVersion, Client, SlidingSyncMode, diff --git a/crates/matrix-sdk-ui/tests/integration/encryption_sync_service.rs b/crates/matrix-sdk-ui/tests/integration/encryption_sync_service.rs index 299dd3f84..6c79c0830 100644 --- a/crates/matrix-sdk-ui/tests/integration/encryption_sync_service.rs +++ b/crates/matrix-sdk-ui/tests/integration/encryption_sync_service.rs @@ -8,8 +8,8 @@ use std::{ use futures_util::{pin_mut, StreamExt as _}; use matrix_sdk::{ + authentication::matrix::{MatrixSession, MatrixSessionTokens}, config::RequestConfig, - matrix_auth::{MatrixSession, MatrixSessionTokens}, test_utils::{logged_in_client_with_server, test_client_builder_with_server}, SessionMeta, }; diff --git a/crates/matrix-sdk/src/matrix_auth/login_builder.rs b/crates/matrix-sdk/src/authentication/matrix/login_builder.rs similarity index 100% rename from crates/matrix-sdk/src/matrix_auth/login_builder.rs rename to crates/matrix-sdk/src/authentication/matrix/login_builder.rs diff --git a/crates/matrix-sdk/src/matrix_auth/mod.rs b/crates/matrix-sdk/src/authentication/matrix/mod.rs similarity index 99% rename from crates/matrix-sdk/src/matrix_auth/mod.rs rename to crates/matrix-sdk/src/authentication/matrix/mod.rs index 8beb76de1..dfadd9ad0 100644 --- a/crates/matrix-sdk/src/matrix_auth/mod.rs +++ b/crates/matrix-sdk/src/authentication/matrix/mod.rs @@ -760,7 +760,7 @@ impl MatrixAuth { /// ```no_run /// use futures_util::StreamExt; /// use matrix_sdk::Client; - /// # fn persist_session(_: &matrix_sdk::matrix_auth::MatrixSession) {}; + /// # fn persist_session(_: &matrix_sdk::authentication::matrix::MatrixSession) {}; /// # async { /// let homeserver = "http://example.com"; /// let client = Client::builder() @@ -835,7 +835,7 @@ impl MatrixAuth { /// /// ```no_run /// use matrix_sdk::{ - /// matrix_auth::{MatrixSession, MatrixSessionTokens}, + /// authentication::matrix::{MatrixSession, MatrixSessionTokens}, /// ruma::{device_id, user_id}, /// Client, SessionMeta, /// }; @@ -881,7 +881,7 @@ impl MatrixAuth { /// ``` /// /// [`login`]: #method.login - /// [`LoginBuilder::send()`]: crate::matrix_auth::LoginBuilder::send + /// [`LoginBuilder::send()`]: crate::authentication::matrix::LoginBuilder::send #[instrument(skip_all)] pub async fn restore_session(&self, session: MatrixSession) -> Result<()> { debug!("Restoring Matrix auth session"); @@ -959,7 +959,7 @@ impl MatrixAuth { /// /// ``` /// use matrix_sdk::{ -/// matrix_auth::{MatrixSession, MatrixSessionTokens}, +/// authentication::matrix::{MatrixSession, MatrixSessionTokens}, /// SessionMeta, /// }; /// use ruma::{device_id, user_id}; diff --git a/crates/matrix-sdk/src/authentication/mod.rs b/crates/matrix-sdk/src/authentication/mod.rs index 408de8680..add20b253 100644 --- a/crates/matrix-sdk/src/authentication/mod.rs +++ b/crates/matrix-sdk/src/authentication/mod.rs @@ -15,7 +15,7 @@ //! Types and functions related to authentication in Matrix. // TODO:(pixlwave) Move AuthenticationService from the FFI into this module. -// TODO:(poljar) Move the oidc and matrix_auth modules under this module. +// TODO:(poljar) Move the oidc module under this module. use std::sync::Arc; @@ -23,12 +23,12 @@ use as_variant::as_variant; use matrix_sdk_base::SessionMeta; use tokio::sync::{broadcast, Mutex, OnceCell}; +pub mod matrix; + +use self::matrix::{MatrixAuth, MatrixAuthData}; #[cfg(feature = "experimental-oidc")] use crate::oidc::{self, Oidc, OidcAuthData, OidcCtx}; -use crate::{ - matrix_auth::{self, MatrixAuth, MatrixAuthData}, - Client, RefreshTokenError, SessionChange, -}; +use crate::{Client, RefreshTokenError, SessionChange}; #[cfg(all(feature = "experimental-oidc", feature = "e2e-encryption", not(target_arch = "wasm32")))] pub mod qrcode; @@ -36,8 +36,8 @@ pub mod qrcode; /// Session tokens, for any kind of authentication. #[allow(missing_debug_implementations, clippy::large_enum_variant)] pub enum SessionTokens { - /// Tokens for a [`matrix_auth`] session. - Matrix(matrix_auth::MatrixSessionTokens), + /// Tokens for a [`matrix`] session. + Matrix(matrix::MatrixSessionTokens), #[cfg(feature = "experimental-oidc")] /// Tokens for an [`oidc`] session. Oidc(oidc::OidcSessionTokens), @@ -103,7 +103,7 @@ pub enum AuthApi { #[non_exhaustive] pub enum AuthSession { /// A session using the native Matrix authentication API. - Matrix(matrix_auth::MatrixSession), + Matrix(matrix::MatrixSession), /// A session using the OpenID Connect API. #[cfg(feature = "experimental-oidc")] @@ -148,8 +148,8 @@ impl AuthSession { } } -impl From for AuthSession { - fn from(session: matrix_auth::MatrixSession) -> Self { +impl From for AuthSession { + fn from(session: matrix::MatrixSession) -> Self { Self::Matrix(session) } } diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index f2daad94a..773c1c54c 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -76,7 +76,9 @@ use self::futures::SendRequest; #[cfg(feature = "experimental-oidc")] use crate::oidc::Oidc; use crate::{ - authentication::{AuthCtx, AuthData, ReloadSessionCallback, SaveSessionCallback}, + authentication::{ + matrix::MatrixAuth, AuthCtx, AuthData, ReloadSessionCallback, SaveSessionCallback, + }, config::RequestConfig, deduplicating_handler::DeduplicatingHandler, error::{HttpError, HttpResult}, @@ -86,7 +88,6 @@ use crate::{ EventHandlerStore, ObservableEventHandler, SyncEvent, }, http_client::HttpClient, - matrix_auth::MatrixAuth, notification_settings::NotificationSettings, room_preview::RoomPreview, send_queue::SendQueueData, diff --git a/crates/matrix-sdk/src/docs/encryption.md b/crates/matrix-sdk/src/docs/encryption.md index 0858260e6..91aa1df6e 100644 --- a/crates/matrix-sdk/src/docs/encryption.md +++ b/crates/matrix-sdk/src/docs/encryption.md @@ -233,4 +233,4 @@ is **not** supported using the default store. [`StoreConfig`]: crate::config::StoreConfig [`ClientBuilder`]: crate::ClientBuilder [`ClientBuilder::store_config`]: crate::ClientBuilder::store_config -[`MatrixAuth::login_username()`]: crate::matrix_auth::MatrixAuth::login_username +[`MatrixAuth::login_username()`]: crate::authentication::matrix::MatrixAuth::login_username diff --git a/crates/matrix-sdk/src/encryption/mod.rs b/crates/matrix-sdk/src/encryption/mod.rs index e59bf74ab..efad584e9 100644 --- a/crates/matrix-sdk/src/encryption/mod.rs +++ b/crates/matrix-sdk/src/encryption/mod.rs @@ -1778,9 +1778,9 @@ mod tests { use crate::{ assert_next_matches_with_timeout, + authentication::matrix::{MatrixSession, MatrixSessionTokens}, config::RequestConfig, encryption::VerificationState, - matrix_auth::{MatrixSession, MatrixSessionTokens}, test_utils::{logged_in_client, no_retry_test_client, set_client_session}, Client, }; diff --git a/crates/matrix-sdk/src/lib.rs b/crates/matrix-sdk/src/lib.rs index bfc7a9bc2..0eac2350a 100644 --- a/crates/matrix-sdk/src/lib.rs +++ b/crates/matrix-sdk/src/lib.rs @@ -45,7 +45,6 @@ mod error; pub mod event_cache; pub mod event_handler; mod http_client; -pub mod matrix_auth; pub mod media; pub mod notification_settings; #[cfg(feature = "experimental-oidc")] diff --git a/crates/matrix-sdk/src/room/mod.rs b/crates/matrix-sdk/src/room/mod.rs index d13175677..fbe5ab16b 100644 --- a/crates/matrix-sdk/src/room/mod.rs +++ b/crates/matrix-sdk/src/room/mod.rs @@ -3702,8 +3702,8 @@ mod tests { use super::ReportedContentScore; use crate::{ + authentication::matrix::{MatrixSession, MatrixSessionTokens}, config::RequestConfig, - matrix_auth::{MatrixSession, MatrixSessionTokens}, test_utils::{logged_in_client, mocks::MatrixMockServer}, Client, }; diff --git a/crates/matrix-sdk/src/sliding_sync/client.rs b/crates/matrix-sdk/src/sliding_sync/client.rs index 2cad13477..7695ae004 100644 --- a/crates/matrix-sdk/src/sliding_sync/client.rs +++ b/crates/matrix-sdk/src/sliding_sync/client.rs @@ -380,8 +380,8 @@ mod tests { use super::{discover_homeserver, get_supported_versions, Version, VersionBuilder}; use crate::{ + authentication::matrix::{MatrixSession, MatrixSessionTokens}, error::Result, - matrix_auth::{MatrixSession, MatrixSessionTokens}, sliding_sync::{http, VersionBuilderError}, test_utils::logged_in_client_with_server, Client, SlidingSyncList, SlidingSyncMode, diff --git a/crates/matrix-sdk/src/test_utils/client.rs b/crates/matrix-sdk/src/test_utils/client.rs index bfbbf539e..b22494077 100644 --- a/crates/matrix-sdk/src/test_utils/client.rs +++ b/crates/matrix-sdk/src/test_utils/client.rs @@ -18,8 +18,8 @@ use matrix_sdk_base::{store::StoreConfig, SessionMeta}; use ruma::{api::MatrixVersion, device_id, user_id}; use crate::{ + authentication::matrix::{MatrixSession, MatrixSessionTokens}, config::RequestConfig, - matrix_auth::{MatrixSession, MatrixSessionTokens}, Client, ClientBuilder, }; diff --git a/crates/matrix-sdk/src/test_utils/mod.rs b/crates/matrix-sdk/src/test_utils/mod.rs index 80865ad2a..6c79fc4c7 100644 --- a/crates/matrix-sdk/src/test_utils/mod.rs +++ b/crates/matrix-sdk/src/test_utils/mod.rs @@ -17,8 +17,8 @@ pub mod client; pub mod mocks; use crate::{ + authentication::matrix::{MatrixSession, MatrixSessionTokens}, config::RequestConfig, - matrix_auth::{MatrixSession, MatrixSessionTokens}, Client, ClientBuilder, }; diff --git a/crates/matrix-sdk/tests/integration/client.rs b/crates/matrix-sdk/tests/integration/client.rs index 2269d0018..aafaaa3fa 100644 --- a/crates/matrix-sdk/tests/integration/client.rs +++ b/crates/matrix-sdk/tests/integration/client.rs @@ -4,8 +4,8 @@ use assert_matches2::{assert_let, assert_matches}; use eyeball_im::VectorDiff; use futures_util::FutureExt; use matrix_sdk::{ + authentication::matrix::{MatrixSession, MatrixSessionTokens}, config::{RequestConfig, StoreConfig, SyncSettings}, - matrix_auth::{MatrixSession, MatrixSessionTokens}, sync::RoomUpdate, test_utils::no_retry_test_client_with_server, Client, MemoryStore, SessionMeta, StateChanges, StateStore, diff --git a/crates/matrix-sdk/tests/integration/encryption/backups.rs b/crates/matrix-sdk/tests/integration/encryption/backups.rs index 5f41f1e39..fe56cf0f0 100644 --- a/crates/matrix-sdk/tests/integration/encryption/backups.rs +++ b/crates/matrix-sdk/tests/integration/encryption/backups.rs @@ -18,6 +18,7 @@ use anyhow::Result; use assert_matches::assert_matches; use futures_util::{pin_mut, FutureExt, StreamExt}; use matrix_sdk::{ + authentication::matrix::{MatrixSession, MatrixSessionTokens}, config::RequestConfig, crypto::{ olm::{InboundGroupSession, SenderData, SessionCreationError}, @@ -29,7 +30,6 @@ use matrix_sdk::{ secret_storage::SecretStore, BackupDownloadStrategy, EncryptionSettings, }, - matrix_auth::{MatrixSession, MatrixSessionTokens}, test_utils::{no_retry_test_client_with_server, test_client_builder_with_server}, Client, }; diff --git a/crates/matrix-sdk/tests/integration/encryption/cross_signing.rs b/crates/matrix-sdk/tests/integration/encryption/cross_signing.rs index 79122252c..033d61391 100644 --- a/crates/matrix-sdk/tests/integration/encryption/cross_signing.rs +++ b/crates/matrix-sdk/tests/integration/encryption/cross_signing.rs @@ -14,8 +14,8 @@ use assert_matches2::assert_let; use matrix_sdk::{ + authentication::matrix::{MatrixSession, MatrixSessionTokens}, encryption::CrossSigningResetAuthType, - matrix_auth::{MatrixSession, MatrixSessionTokens}, test_utils::no_retry_test_client_with_server, SessionMeta, }; diff --git a/crates/matrix-sdk/tests/integration/encryption/recovery.rs b/crates/matrix-sdk/tests/integration/encryption/recovery.rs index eada062a1..6f49a4232 100644 --- a/crates/matrix-sdk/tests/integration/encryption/recovery.rs +++ b/crates/matrix-sdk/tests/integration/encryption/recovery.rs @@ -17,13 +17,13 @@ use std::sync::{Arc, Mutex}; use assert_matches2::assert_let; use futures_util::StreamExt; use matrix_sdk::{ + authentication::matrix::{MatrixSession, MatrixSessionTokens}, config::RequestConfig, encryption::{ backups::BackupState, recovery::{EnableProgress, RecoveryState}, BackupDownloadStrategy, CrossSigningResetAuthType, }, - matrix_auth::{MatrixSession, MatrixSessionTokens}, test_utils::{no_retry_test_client_with_server, test_client_builder_with_server}, Client, }; diff --git a/crates/matrix-sdk/tests/integration/encryption/secret_storage.rs b/crates/matrix-sdk/tests/integration/encryption/secret_storage.rs index 147ed871b..fb62d506f 100644 --- a/crates/matrix-sdk/tests/integration/encryption/secret_storage.rs +++ b/crates/matrix-sdk/tests/integration/encryption/secret_storage.rs @@ -2,8 +2,8 @@ use std::sync::{Arc, Mutex}; use assert_matches::assert_matches; use matrix_sdk::{ + authentication::matrix::{MatrixSession, MatrixSessionTokens}, encryption::secret_storage::SecretStorageError, - matrix_auth::{MatrixSession, MatrixSessionTokens}, test_utils::no_retry_test_client_with_server, }; use matrix_sdk_base::SessionMeta; diff --git a/crates/matrix-sdk/tests/integration/encryption/verification.rs b/crates/matrix-sdk/tests/integration/encryption/verification.rs index 69bc38514..ece7be7c0 100644 --- a/crates/matrix-sdk/tests/integration/encryption/verification.rs +++ b/crates/matrix-sdk/tests/integration/encryption/verification.rs @@ -7,9 +7,9 @@ use assert_matches2::assert_matches; use futures_util::FutureExt; use imbl::HashSet; use matrix_sdk::{ + authentication::matrix::{MatrixSession, MatrixSessionTokens}, config::RequestConfig, encryption::VerificationState, - matrix_auth::{MatrixSession, MatrixSessionTokens}, test_utils::logged_in_client_with_server, Client, }; diff --git a/crates/matrix-sdk/tests/integration/matrix_auth.rs b/crates/matrix-sdk/tests/integration/matrix_auth.rs index ef732b37f..6bd2e0a16 100644 --- a/crates/matrix-sdk/tests/integration/matrix_auth.rs +++ b/crates/matrix-sdk/tests/integration/matrix_auth.rs @@ -2,8 +2,8 @@ use std::{collections::BTreeMap, sync::Mutex}; use assert_matches::assert_matches; use matrix_sdk::{ + authentication::matrix::{MatrixSession, MatrixSessionTokens}, config::RequestConfig, - matrix_auth::{MatrixSession, MatrixSessionTokens}, test_utils::{logged_in_client_with_server, no_retry_test_client_with_server}, AuthApi, AuthSession, Client, RumaApiError, }; diff --git a/crates/matrix-sdk/tests/integration/media.rs b/crates/matrix-sdk/tests/integration/media.rs index ec7032919..75d78d963 100644 --- a/crates/matrix-sdk/tests/integration/media.rs +++ b/crates/matrix-sdk/tests/integration/media.rs @@ -1,6 +1,6 @@ use matrix_sdk::{ + authentication::matrix::{MatrixSession, MatrixSessionTokens}, config::RequestConfig, - matrix_auth::{MatrixSession, MatrixSessionTokens}, media::{MediaFormat, MediaRequestParameters, MediaThumbnailSettings}, test_utils::logged_in_client_with_server, Client, SessionMeta, diff --git a/crates/matrix-sdk/tests/integration/refresh_token.rs b/crates/matrix-sdk/tests/integration/refresh_token.rs index 6067698c6..c2dbcaee6 100644 --- a/crates/matrix-sdk/tests/integration/refresh_token.rs +++ b/crates/matrix-sdk/tests/integration/refresh_token.rs @@ -7,9 +7,9 @@ use assert_matches::assert_matches; use assert_matches2::assert_let; use futures_util::StreamExt; use matrix_sdk::{ + authentication::matrix::{MatrixSession, MatrixSessionTokens}, config::RequestConfig, executor::spawn, - matrix_auth::{MatrixSession, MatrixSessionTokens}, test_utils::{ logged_in_client_with_server, no_retry_test_client_with_server, test_client_builder_with_server, diff --git a/examples/persist_session/src/main.rs b/examples/persist_session/src/main.rs index b3b9a9e1c..248612ccc 100644 --- a/examples/persist_session/src/main.rs +++ b/examples/persist_session/src/main.rs @@ -4,8 +4,8 @@ use std::{ }; use matrix_sdk::{ + authentication::matrix::MatrixSession, config::SyncSettings, - matrix_auth::MatrixSession, ruma::{ api::client::filter::FilterDefinition, events::room::message::{MessageType, OriginalSyncRoomMessageEvent}, diff --git a/examples/secret_storage/src/main.rs b/examples/secret_storage/src/main.rs index 1175c4612..92196fceb 100644 --- a/examples/secret_storage/src/main.rs +++ b/examples/secret_storage/src/main.rs @@ -1,8 +1,8 @@ use anyhow::Result; use clap::{Parser, Subcommand}; use matrix_sdk::{ + authentication::matrix::{MatrixSession, MatrixSessionTokens}, encryption::secret_storage::SecretStore, - matrix_auth::{MatrixSession, MatrixSessionTokens}, ruma::{events::secret::request::SecretName, OwnedDeviceId, OwnedUserId}, AuthSession, Client, SessionMeta, }; diff --git a/labs/multiverse/src/main.rs b/labs/multiverse/src/main.rs index e1d76ce4f..aab3b626f 100644 --- a/labs/multiverse/src/main.rs +++ b/labs/multiverse/src/main.rs @@ -17,9 +17,9 @@ use crossterm::{ use futures_util::{pin_mut, StreamExt as _}; use imbl::Vector; use matrix_sdk::{ + authentication::matrix::MatrixSession, config::StoreConfig, encryption::{BackupDownloadStrategy, EncryptionSettings}, - matrix_auth::MatrixSession, ruma::{ api::client::receipt::create_receipt::v3::ReceiptType, events::room::message::{MessageType, RoomMessageEventContent}, From 3e78e441d4e8209c8f9f076394edf1b29811cd07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Wed, 22 Jan 2025 17:09:02 +0100 Subject: [PATCH 06/55] refactor(sdk): Move oidc module to authentication::oidc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Kévin Commaille --- bindings/matrix-sdk-ffi/src/authentication.rs | 2 +- bindings/matrix-sdk-ffi/src/client.rs | 18 +++++++++--------- bindings/matrix-sdk-ffi/src/error.rs | 4 ++-- crates/matrix-sdk/src/authentication/mod.rs | 5 +++-- .../oidc/auth_code_builder.rs | 0 .../{ => authentication}/oidc/backend/mock.rs | 2 +- .../{ => authentication}/oidc/backend/mod.rs | 0 .../oidc/backend/server.rs | 2 +- .../{ => authentication}/oidc/cross_process.rs | 2 +- .../{ => authentication}/oidc/data_serde.rs | 0 .../oidc/end_session_builder.rs | 0 .../src/{ => authentication}/oidc/mod.rs | 2 +- .../{ => authentication}/oidc/registrations.rs | 0 .../src/{ => authentication}/oidc/tests.rs | 7 ++----- .../src/authentication/qrcode/login.rs | 2 +- .../src/authentication/qrcode/mod.rs | 6 +++--- .../src/authentication/qrcode/oidc_client.rs | 2 +- crates/matrix-sdk/src/client/builder/mod.rs | 4 ++-- crates/matrix-sdk/src/client/futures.rs | 2 +- crates/matrix-sdk/src/client/mod.rs | 2 +- crates/matrix-sdk/src/error.rs | 4 ++-- crates/matrix-sdk/src/lib.rs | 2 -- .../integration/encryption/cross_signing.rs | 2 +- examples/oidc_cli/src/main.rs | 6 +++--- examples/qr-login/src/main.rs | 14 ++++++++------ 25 files changed, 44 insertions(+), 46 deletions(-) rename crates/matrix-sdk/src/{ => authentication}/oidc/auth_code_builder.rs (100%) rename crates/matrix-sdk/src/{ => authentication}/oidc/backend/mock.rs (99%) rename crates/matrix-sdk/src/{ => authentication}/oidc/backend/mod.rs (100%) rename crates/matrix-sdk/src/{ => authentication}/oidc/backend/server.rs (98%) rename crates/matrix-sdk/src/{ => authentication}/oidc/cross_process.rs (99%) rename crates/matrix-sdk/src/{ => authentication}/oidc/data_serde.rs (100%) rename crates/matrix-sdk/src/{ => authentication}/oidc/end_session_builder.rs (100%) rename crates/matrix-sdk/src/{ => authentication}/oidc/mod.rs (99%) rename crates/matrix-sdk/src/{ => authentication}/oidc/registrations.rs (100%) rename crates/matrix-sdk/src/{ => authentication}/oidc/tests.rs (99%) diff --git a/bindings/matrix-sdk-ffi/src/authentication.rs b/bindings/matrix-sdk-ffi/src/authentication.rs index a2e41a5f9..3cbf57654 100644 --- a/bindings/matrix-sdk-ffi/src/authentication.rs +++ b/bindings/matrix-sdk-ffi/src/authentication.rs @@ -5,7 +5,7 @@ use std::{ }; use matrix_sdk::{ - oidc::{ + authentication::oidc::{ registrations::OidcRegistrationsError, types::{ iana::oauth::OAuthClientAuthenticationMethod, diff --git a/bindings/matrix-sdk-ffi/src/client.rs b/bindings/matrix-sdk-ffi/src/client.rs index 66d2f0256..d2431c25e 100644 --- a/bindings/matrix-sdk-ffi/src/client.rs +++ b/bindings/matrix-sdk-ffi/src/client.rs @@ -7,11 +7,7 @@ use std::{ use anyhow::{anyhow, Context as _}; use matrix_sdk::{ - media::{ - MediaFileHandle as SdkMediaFileHandle, MediaFormat, MediaRequestParameters, - MediaThumbnailSettings, - }, - oidc::{ + authentication::oidc::{ registrations::{ClientId, OidcRegistrations}, requests::account_management::AccountManagementActionFull, types::{ @@ -23,6 +19,10 @@ use matrix_sdk::{ }, OidcAuthorizationData, OidcSession, }, + media::{ + MediaFileHandle as SdkMediaFileHandle, MediaFormat, MediaRequestParameters, + MediaThumbnailSettings, + }, reqwest::StatusCode, ruma::{ api::client::{ @@ -1556,10 +1556,10 @@ impl Session { } // Build the session from the OIDC UserSession. AuthApi::Oidc(api) => { - let matrix_sdk::oidc::UserSession { + let matrix_sdk::authentication::oidc::UserSession { meta: matrix_sdk::SessionMeta { user_id, device_id }, tokens: - matrix_sdk::oidc::OidcSessionTokens { + matrix_sdk::authentication::oidc::OidcSessionTokens { access_token, refresh_token, latest_id_token, @@ -1620,12 +1620,12 @@ impl TryFrom for AuthSession { .transpose() .context("OIDC latest_id_token is invalid.")?; - let user_session = matrix_sdk::oidc::UserSession { + let user_session = matrix_sdk::authentication::oidc::UserSession { meta: matrix_sdk::SessionMeta { user_id: user_id.try_into()?, device_id: device_id.into(), }, - tokens: matrix_sdk::oidc::OidcSessionTokens { + tokens: matrix_sdk::authentication::oidc::OidcSessionTokens { access_token, refresh_token, latest_id_token, diff --git a/bindings/matrix-sdk-ffi/src/error.rs b/bindings/matrix-sdk-ffi/src/error.rs index 9f630a767..7e42d246f 100644 --- a/bindings/matrix-sdk-ffi/src/error.rs +++ b/bindings/matrix-sdk-ffi/src/error.rs @@ -1,8 +1,8 @@ use std::{collections::HashMap, fmt, fmt::Display}; use matrix_sdk::{ - encryption::CryptoStoreError, event_cache::EventCacheError, oidc::OidcError, reqwest, - room::edit::EditError, send_queue::RoomSendQueueError, HttpError, IdParseError, + authentication::oidc::OidcError, encryption::CryptoStoreError, event_cache::EventCacheError, + reqwest, room::edit::EditError, send_queue::RoomSendQueueError, HttpError, IdParseError, NotificationSettingsError as SdkNotificationSettingsError, QueueWedgeError as SdkQueueWedgeError, StoreError, }; diff --git a/crates/matrix-sdk/src/authentication/mod.rs b/crates/matrix-sdk/src/authentication/mod.rs index add20b253..eafd9c38a 100644 --- a/crates/matrix-sdk/src/authentication/mod.rs +++ b/crates/matrix-sdk/src/authentication/mod.rs @@ -15,7 +15,6 @@ //! Types and functions related to authentication in Matrix. // TODO:(pixlwave) Move AuthenticationService from the FFI into this module. -// TODO:(poljar) Move the oidc module under this module. use std::sync::Arc; @@ -24,10 +23,12 @@ use matrix_sdk_base::SessionMeta; use tokio::sync::{broadcast, Mutex, OnceCell}; pub mod matrix; +#[cfg(feature = "experimental-oidc")] +pub mod oidc; use self::matrix::{MatrixAuth, MatrixAuthData}; #[cfg(feature = "experimental-oidc")] -use crate::oidc::{self, Oidc, OidcAuthData, OidcCtx}; +use self::oidc::{Oidc, OidcAuthData, OidcCtx}; use crate::{Client, RefreshTokenError, SessionChange}; #[cfg(all(feature = "experimental-oidc", feature = "e2e-encryption", not(target_arch = "wasm32")))] diff --git a/crates/matrix-sdk/src/oidc/auth_code_builder.rs b/crates/matrix-sdk/src/authentication/oidc/auth_code_builder.rs similarity index 100% rename from crates/matrix-sdk/src/oidc/auth_code_builder.rs rename to crates/matrix-sdk/src/authentication/oidc/auth_code_builder.rs diff --git a/crates/matrix-sdk/src/oidc/backend/mock.rs b/crates/matrix-sdk/src/authentication/oidc/backend/mock.rs similarity index 99% rename from crates/matrix-sdk/src/oidc/backend/mock.rs rename to crates/matrix-sdk/src/authentication/oidc/backend/mock.rs index de3573967..e6e67f0ae 100644 --- a/crates/matrix-sdk/src/oidc/backend/mock.rs +++ b/crates/matrix-sdk/src/authentication/oidc/backend/mock.rs @@ -37,7 +37,7 @@ use mas_oidc_client::{ use url::Url; use super::{OidcBackend, OidcError, RefreshedSessionTokens}; -use crate::oidc::{AuthorizationCode, OidcSessionTokens}; +use crate::authentication::oidc::{AuthorizationCode, OidcSessionTokens}; pub(crate) const ISSUER_URL: &str = "https://oidc.example.com/issuer"; pub(crate) const AUTHORIZATION_URL: &str = "https://oidc.example.com/authorization"; diff --git a/crates/matrix-sdk/src/oidc/backend/mod.rs b/crates/matrix-sdk/src/authentication/oidc/backend/mod.rs similarity index 100% rename from crates/matrix-sdk/src/oidc/backend/mod.rs rename to crates/matrix-sdk/src/authentication/oidc/backend/mod.rs diff --git a/crates/matrix-sdk/src/oidc/backend/server.rs b/crates/matrix-sdk/src/authentication/oidc/backend/server.rs similarity index 98% rename from crates/matrix-sdk/src/oidc/backend/server.rs rename to crates/matrix-sdk/src/authentication/oidc/backend/server.rs index b2ea157aa..a97efc20d 100644 --- a/crates/matrix-sdk/src/oidc/backend/server.rs +++ b/crates/matrix-sdk/src/authentication/oidc/backend/server.rs @@ -42,7 +42,7 @@ use url::Url; use super::{OidcBackend, OidcError, RefreshedSessionTokens}; use crate::{ - oidc::{rng, AuthorizationCode, OidcSessionTokens}, + authentication::oidc::{rng, AuthorizationCode, OidcSessionTokens}, Client, }; diff --git a/crates/matrix-sdk/src/oidc/cross_process.rs b/crates/matrix-sdk/src/authentication/oidc/cross_process.rs similarity index 99% rename from crates/matrix-sdk/src/oidc/cross_process.rs rename to crates/matrix-sdk/src/authentication/oidc/cross_process.rs index 87c057d17..3a41f3e0a 100644 --- a/crates/matrix-sdk/src/oidc/cross_process.rs +++ b/crates/matrix-sdk/src/authentication/oidc/cross_process.rs @@ -264,7 +264,7 @@ mod tests { use super::compute_session_hash; use crate::{ - oidc::{ + authentication::oidc::{ backend::mock::{MockImpl, ISSUER_URL}, cross_process::SessionHash, tests, diff --git a/crates/matrix-sdk/src/oidc/data_serde.rs b/crates/matrix-sdk/src/authentication/oidc/data_serde.rs similarity index 100% rename from crates/matrix-sdk/src/oidc/data_serde.rs rename to crates/matrix-sdk/src/authentication/oidc/data_serde.rs diff --git a/crates/matrix-sdk/src/oidc/end_session_builder.rs b/crates/matrix-sdk/src/authentication/oidc/end_session_builder.rs similarity index 100% rename from crates/matrix-sdk/src/oidc/end_session_builder.rs rename to crates/matrix-sdk/src/authentication/oidc/end_session_builder.rs diff --git a/crates/matrix-sdk/src/oidc/mod.rs b/crates/matrix-sdk/src/authentication/oidc/mod.rs similarity index 99% rename from crates/matrix-sdk/src/oidc/mod.rs rename to crates/matrix-sdk/src/authentication/oidc/mod.rs index 5fb525d75..db42bd95a 100644 --- a/crates/matrix-sdk/src/oidc/mod.rs +++ b/crates/matrix-sdk/src/authentication/oidc/mod.rs @@ -217,11 +217,11 @@ pub use self::{ use self::{ backend::{server::OidcServer, OidcBackend}, cross_process::{CrossProcessRefreshLockGuard, CrossProcessRefreshManager}, + registrations::{ClientId, OidcRegistrations}, }; use crate::{ authentication::{qrcode::LoginWithQrCode, AuthData}, client::SessionChange, - oidc::registrations::{ClientId, OidcRegistrations}, Client, HttpError, RefreshTokenError, Result, }; diff --git a/crates/matrix-sdk/src/oidc/registrations.rs b/crates/matrix-sdk/src/authentication/oidc/registrations.rs similarity index 100% rename from crates/matrix-sdk/src/oidc/registrations.rs rename to crates/matrix-sdk/src/authentication/oidc/registrations.rs diff --git a/crates/matrix-sdk/src/oidc/tests.rs b/crates/matrix-sdk/src/authentication/oidc/tests.rs similarity index 99% rename from crates/matrix-sdk/src/oidc/tests.rs rename to crates/matrix-sdk/src/authentication/oidc/tests.rs index 0c40cff89..2a01657fc 100644 --- a/crates/matrix-sdk/src/oidc/tests.rs +++ b/crates/matrix-sdk/src/authentication/oidc/tests.rs @@ -26,14 +26,11 @@ use wiremock::{ use super::{ backend::mock::{MockImpl, AUTHORIZATION_URL, ISSUER_URL}, + registrations::{ClientId, OidcRegistrations}, AuthorizationCode, AuthorizationError, AuthorizationResponse, Oidc, OidcError, OidcSession, OidcSessionTokens, RedirectUriQueryParseError, UserSession, }; -use crate::{ - oidc::registrations::{ClientId, OidcRegistrations}, - test_utils::test_client_builder, - Client, Error, -}; +use crate::{test_utils::test_client_builder, Client, Error}; const CLIENT_ID: &str = "test_client_id"; const REDIRECT_URI_STRING: &str = "http://matrix.example.com/oidc/callback"; diff --git a/crates/matrix-sdk/src/authentication/qrcode/login.rs b/crates/matrix-sdk/src/authentication/qrcode/login.rs index 4422d6b07..9bf13daaa 100644 --- a/crates/matrix-sdk/src/authentication/qrcode/login.rs +++ b/crates/matrix-sdk/src/authentication/qrcode/login.rs @@ -34,7 +34,7 @@ use super::{ SecureChannelError, }; #[cfg(doc)] -use crate::oidc::Oidc; +use crate::authentication::oidc::Oidc; use crate::{ authentication::qrcode::{ messages::QrAuthMessage, secure_channel::EstablishedSecureChannel, QRCodeLoginError, diff --git a/crates/matrix-sdk/src/authentication/qrcode/mod.rs b/crates/matrix-sdk/src/authentication/qrcode/mod.rs index 752f21e0c..0044f36a6 100644 --- a/crates/matrix-sdk/src/authentication/qrcode/mod.rs +++ b/crates/matrix-sdk/src/authentication/qrcode/mod.rs @@ -33,8 +33,8 @@ use url::Url; pub use vodozemac::ecies::{Error as EciesError, MessageDecodeError}; #[cfg(doc)] -use crate::oidc::Oidc; -use crate::{oidc::CrossProcessRefreshLockError, HttpError}; +use crate::authentication::oidc::Oidc; +use crate::{authentication::oidc::CrossProcessRefreshLockError, HttpError}; mod login; mod messages; @@ -113,7 +113,7 @@ pub enum DeviceAuhorizationOidcError { /// A generic OIDC error happened while we were attempting to register the /// device with the OIDC provider. #[error(transparent)] - Oidc(#[from] crate::oidc::OidcError), + Oidc(#[from] crate::authentication::oidc::OidcError), /// The issuer URL failed to be parsed. #[error(transparent)] diff --git a/crates/matrix-sdk/src/authentication/qrcode/oidc_client.rs b/crates/matrix-sdk/src/authentication/qrcode/oidc_client.rs index 3cdc3a0a2..1adce855a 100644 --- a/crates/matrix-sdk/src/authentication/qrcode/oidc_client.rs +++ b/crates/matrix-sdk/src/authentication/qrcode/oidc_client.rs @@ -32,7 +32,7 @@ use openidconnect::{ use vodozemac::Curve25519PublicKey; use super::DeviceAuhorizationOidcError; -use crate::{http_client::HttpClient, oidc::OidcSessionTokens}; +use crate::{authentication::oidc::OidcSessionTokens, http_client::HttpClient}; // Obtain the device_authorization_url from the OIDC metadata provider. #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] diff --git a/crates/matrix-sdk/src/client/builder/mod.rs b/crates/matrix-sdk/src/client/builder/mod.rs index 8902f2360..bf4ab18ca 100644 --- a/crates/matrix-sdk/src/client/builder/mod.rs +++ b/crates/matrix-sdk/src/client/builder/mod.rs @@ -28,14 +28,14 @@ use tokio::sync::{broadcast, Mutex, OnceCell}; use tracing::{debug, field::debug, instrument, Span}; use super::{Client, ClientInner}; +#[cfg(feature = "experimental-oidc")] +use crate::authentication::oidc::OidcCtx; #[cfg(feature = "e2e-encryption")] use crate::crypto::{CollectStrategy, TrustRequirement}; #[cfg(feature = "e2e-encryption")] use crate::encryption::EncryptionSettings; #[cfg(not(target_arch = "wasm32"))] use crate::http_client::HttpSettings; -#[cfg(feature = "experimental-oidc")] -use crate::oidc::OidcCtx; use crate::{ authentication::AuthCtx, client::ClientServerCapabilities, config::RequestConfig, error::RumaApiError, http_client::HttpClient, send_queue::SendQueueData, diff --git a/crates/matrix-sdk/src/client/futures.rs b/crates/matrix-sdk/src/client/futures.rs index 7c828cc7b..4cd5ebce1 100644 --- a/crates/matrix-sdk/src/client/futures.rs +++ b/crates/matrix-sdk/src/client/futures.rs @@ -35,7 +35,7 @@ use tracing::trace; use super::super::Client; #[cfg(feature = "experimental-oidc")] -use crate::oidc::OidcError; +use crate::authentication::oidc::OidcError; use crate::{ config::RequestConfig, error::{HttpError, HttpResult}, diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index 773c1c54c..27d6ece52 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -74,7 +74,7 @@ use url::Url; use self::futures::SendRequest; #[cfg(feature = "experimental-oidc")] -use crate::oidc::Oidc; +use crate::authentication::oidc::Oidc; use crate::{ authentication::{ matrix::MatrixAuth, AuthCtx, AuthData, ReloadSessionCallback, SaveSessionCallback, diff --git a/crates/matrix-sdk/src/error.rs b/crates/matrix-sdk/src/error.rs index 99658d2fc..241ebab8f 100644 --- a/crates/matrix-sdk/src/error.rs +++ b/crates/matrix-sdk/src/error.rs @@ -354,7 +354,7 @@ pub enum Error { /// An error occurred interacting with the OpenID Connect API. #[cfg(feature = "experimental-oidc")] #[error(transparent)] - Oidc(#[from] crate::oidc::OidcError), + Oidc(#[from] crate::authentication::oidc::OidcError), /// A concurrent request to a deduplicated request has failed. #[error("a concurrent request failed; see logs for details")] @@ -561,7 +561,7 @@ pub enum RefreshTokenError { /// An error occurred interacting with the OpenID Connect API. #[cfg(feature = "experimental-oidc")] #[error(transparent)] - Oidc(#[from] Arc), + Oidc(#[from] Arc), } /// Errors that can occur when manipulating push notification settings. diff --git a/crates/matrix-sdk/src/lib.rs b/crates/matrix-sdk/src/lib.rs index 0eac2350a..4f4d4b217 100644 --- a/crates/matrix-sdk/src/lib.rs +++ b/crates/matrix-sdk/src/lib.rs @@ -47,8 +47,6 @@ pub mod event_handler; mod http_client; pub mod media; pub mod notification_settings; -#[cfg(feature = "experimental-oidc")] -pub mod oidc; pub mod pusher; pub mod room; pub mod room_directory_search; diff --git a/crates/matrix-sdk/tests/integration/encryption/cross_signing.rs b/crates/matrix-sdk/tests/integration/encryption/cross_signing.rs index 033d61391..46274a744 100644 --- a/crates/matrix-sdk/tests/integration/encryption/cross_signing.rs +++ b/crates/matrix-sdk/tests/integration/encryption/cross_signing.rs @@ -129,8 +129,8 @@ async fn test_reset_oidc() { registration::{ClientMetadata, VerifiedClientMetadata}, }; use matrix_sdk::{ + authentication::oidc::{OidcSession, OidcSessionTokens, UserSession}, encryption::CrossSigningResetAuthType, - oidc::{OidcSession, OidcSessionTokens, UserSession}, }; use similar_asserts::assert_eq; use url::Url; diff --git a/examples/oidc_cli/src/main.rs b/examples/oidc_cli/src/main.rs index 8761bcef3..13d0d82c8 100644 --- a/examples/oidc_cli/src/main.rs +++ b/examples/oidc_cli/src/main.rs @@ -30,9 +30,7 @@ use axum::{ }; use futures_util::StreamExt; use matrix_sdk::{ - config::SyncSettings, - encryption::{recovery::RecoveryState, CrossSigningResetAuthType}, - oidc::{ + authentication::oidc::{ requests::account_management::AccountManagementActionFull, types::{ client_credentials::ClientCredentials, @@ -44,6 +42,8 @@ use matrix_sdk::{ }, AuthorizationCode, AuthorizationResponse, OidcAuthorizationData, OidcSession, UserSession, }, + config::SyncSettings, + encryption::{recovery::RecoveryState, CrossSigningResetAuthType}, room::Room, ruma::events::room::message::{MessageType, OriginalSyncRoomMessageEvent}, Client, ClientBuildError, Result, RoomState, diff --git a/examples/qr-login/src/main.rs b/examples/qr-login/src/main.rs index 665c511b1..67699bd9c 100644 --- a/examples/qr-login/src/main.rs +++ b/examples/qr-login/src/main.rs @@ -4,12 +4,14 @@ use anyhow::{bail, Context, Result}; use clap::Parser; use futures_util::StreamExt; use matrix_sdk::{ - authentication::qrcode::{LoginProgress, QrCodeData, QrCodeModeData}, - oidc::types::{ - iana::oauth::OAuthClientAuthenticationMethod, - oidc::ApplicationType, - registration::{ClientMetadata, Localized, VerifiedClientMetadata}, - requests::GrantType, + authentication::{ + oidc::types::{ + iana::oauth::OAuthClientAuthenticationMethod, + oidc::ApplicationType, + registration::{ClientMetadata, Localized, VerifiedClientMetadata}, + requests::GrantType, + }, + qrcode::{LoginProgress, QrCodeData, QrCodeModeData}, }, Client, }; From df51404a1464348ff4e75706888516313ccfabdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Wed, 22 Jan 2025 17:17:03 +0100 Subject: [PATCH 07/55] chore(sdk): Add changelog for move of `matrix_auth` and `oidc` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Kévin Commaille --- crates/matrix-sdk/CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/matrix-sdk/CHANGELOG.md b/crates/matrix-sdk/CHANGELOG.md index 84257ac97..c5397b70f 100644 --- a/crates/matrix-sdk/CHANGELOG.md +++ b/crates/matrix-sdk/CHANGELOG.md @@ -46,6 +46,12 @@ All notable changes to this project will be documented in this file. - [**breaking**] `Recovery::are_we_the_last_man_standing()` has been renamed to `is_last_device()`. ([#4522](https://github.com/matrix-org/matrix-rust-sdk/pull/4522)) +- [**breaking**] The `matrix_auth` module is now at `authentication::matrix`. + ([#4575](https://github.com/matrix-org/matrix-rust-sdk/pull/4575)) + +- [**breaking**] The `oidc` module is now at `authentication::oidc`. + ([#4575](https://github.com/matrix-org/matrix-rust-sdk/pull/4575)) + ## [0.9.0] - 2024-12-18 ### Bug Fixes From eb31f035e64a77ed8e5c18ea49ffc06fe78fe26a Mon Sep 17 00:00:00 2001 From: Benjamin Bouvier Date: Tue, 21 Jan 2025 15:28:32 +0100 Subject: [PATCH 08/55] refactor: turn `TimelineEvent` into `SyncTimelineEvent` As the comment noted, they're essentially doing the same thing. A `TimelineEvent` may not have computed push actions, and in that regard it seemed more correct than `SyncTimelineEvent`, so another commit will make the field optional. --- .../src/deserialized_responses.rs | 97 +------------------ .../matrix-sdk-ui/src/notification_client.rs | 21 ++-- .../src/timeline/controller/state.rs | 12 ++- .../timeline/event_item/content/message.rs | 6 +- .../matrix-sdk-ui/src/timeline/tests/mod.rs | 4 +- crates/matrix-sdk-ui/src/timeline/traits.rs | 16 ++- .../matrix-sdk-ui/tests/integration/main.rs | 12 ++- .../tests/integration/timeline/focus_event.rs | 24 ++--- .../integration/timeline/pinned_event.rs | 4 +- crates/matrix-sdk/src/event_cache/mod.rs | 4 +- .../matrix-sdk/src/event_cache/paginator.rs | 49 ++++------ crates/matrix-sdk/src/room/messages.rs | 10 +- crates/matrix-sdk/src/room/mod.rs | 25 +++-- crates/matrix-sdk/src/sliding_sync/room.rs | 9 +- crates/matrix-sdk/src/test_utils/mocks.rs | 4 +- .../tests/integration/room/common.rs | 2 +- .../tests/integration/room/joined.rs | 2 +- .../tests/integration/send_queue.rs | 6 +- testing/matrix-sdk-test/src/event_factory.rs | 6 +- 19 files changed, 110 insertions(+), 203 deletions(-) diff --git a/crates/matrix-sdk-common/src/deserialized_responses.rs b/crates/matrix-sdk-common/src/deserialized_responses.rs index cf27dae0d..9156eb576 100644 --- a/crates/matrix-sdk-common/src/deserialized_responses.rs +++ b/crates/matrix-sdk-common/src/deserialized_responses.rs @@ -15,7 +15,7 @@ use std::{collections::BTreeMap, fmt}; use ruma::{ - events::{AnyMessageLikeEvent, AnySyncTimelineEvent, AnyTimelineEvent}, + events::{AnyMessageLikeEvent, AnySyncTimelineEvent}, push::Action, serde::{ AsRefStr, AsStrAsRefStr, DebugAsRefStr, DeserializeFromCowStr, FromString, JsonObject, Raw, @@ -416,16 +416,9 @@ impl SyncTimelineEvent { } } -impl From for SyncTimelineEvent { - fn from(o: TimelineEvent) -> Self { - Self { kind: o.kind, push_actions: o.push_actions.unwrap_or_default() } - } -} - impl From for SyncTimelineEvent { fn from(decrypted: DecryptedRoomEvent) -> Self { - let timeline_event: TimelineEvent = decrypted.into(); - timeline_event.into() + Self { kind: TimelineEventKind::Decrypted(decrypted), push_actions: Vec::new() } } } @@ -471,72 +464,6 @@ impl<'de> Deserialize<'de> for SyncTimelineEvent { } } -/// Represents a matrix room event that has been returned from a Matrix -/// client-server API endpoint such as `/messages`, after initial processing. -/// -/// The "initial processing" includes an attempt to decrypt encrypted events, so -/// the main thing this adds over [`AnyTimelineEvent`] is information on -/// encryption. -/// -/// Previously, this differed from [`SyncTimelineEvent`] by wrapping an -/// [`AnyTimelineEvent`] instead of an [`AnySyncTimelineEvent`], but nowadays -/// they are essentially identical, and one of them should probably be removed. -#[derive(Clone, Debug)] -pub struct TimelineEvent { - /// The event itself, together with any information on decryption. - pub kind: TimelineEventKind, - - /// The push actions associated with this event, if we had sufficient - /// context to compute them. - pub push_actions: Option>, -} - -impl TimelineEvent { - /// Create a new `TimelineEvent` from the given raw event. - /// - /// This is a convenience constructor for a plaintext event when you don't - /// need to set `push_action`, for example inside a test. - pub fn new(event: Raw) -> Self { - Self { - // This conversion is unproblematic since a `SyncTimelineEvent` is just a - // `TimelineEvent` without the `room_id`. By converting the raw value in - // this way, we simply cause the `room_id` field in the json to be - // ignored by a subsequent deserialization. - kind: TimelineEventKind::PlainText { event: event.cast() }, - push_actions: None, - } - } - - /// Create a new `TimelineEvent` to represent the given decryption failure. - pub fn new_utd_event(event: Raw, utd_info: UnableToDecryptInfo) -> Self { - Self { kind: TimelineEventKind::UnableToDecrypt { event, utd_info }, push_actions: None } - } - - /// Returns a reference to the (potentially decrypted) Matrix event inside - /// this `TimelineEvent`. - pub fn raw(&self) -> &Raw { - self.kind.raw() - } - - /// If the event was a decrypted event that was successfully decrypted, get - /// its encryption info. Otherwise, `None`. - pub fn encryption_info(&self) -> Option<&EncryptionInfo> { - self.kind.encryption_info() - } - - /// Takes ownership of this `TimelineEvent`, returning the (potentially - /// decrypted) Matrix event within. - pub fn into_raw(self) -> Raw { - self.kind.into_raw() - } -} - -impl From for TimelineEvent { - fn from(decrypted: DecryptedRoomEvent) -> Self { - Self { kind: TimelineEventKind::Decrypted(decrypted), push_actions: None } - } -} - /// The event within a [`TimelineEvent`] or [`SyncTimelineEvent`], together with /// encryption data. #[derive(Clone, Serialize, Deserialize)] @@ -957,17 +884,15 @@ mod tests { use assert_matches::assert_matches; use insta::{assert_json_snapshot, with_settings}; use ruma::{ - device_id, event_id, - events::{room::message::RoomMessageEventContent, AnySyncTimelineEvent}, - serde::Raw, - user_id, DeviceKeyAlgorithm, + device_id, event_id, events::room::message::RoomMessageEventContent, serde::Raw, user_id, + DeviceKeyAlgorithm, }; use serde::Deserialize; use serde_json::json; use super::{ AlgorithmInfo, DecryptedRoomEvent, DeviceLinkProblem, EncryptionInfo, ShieldState, - ShieldStateCode, SyncTimelineEvent, TimelineEvent, TimelineEventKind, UnableToDecryptInfo, + ShieldStateCode, SyncTimelineEvent, TimelineEventKind, UnableToDecryptInfo, UnableToDecryptReason, UnsignedDecryptionResult, UnsignedEventLocation, VerificationLevel, VerificationState, WithheldCode, }; @@ -993,18 +918,6 @@ mod tests { ); } - #[test] - fn room_event_to_sync_room_event() { - let room_event = TimelineEvent::new(Raw::new(&example_event()).unwrap().cast()); - let converted_room_event: SyncTimelineEvent = room_event.into(); - - let converted_event: AnySyncTimelineEvent = - converted_room_event.raw().deserialize().unwrap(); - - assert_eq!(converted_event.event_id(), "$xxxxx:example.org"); - assert_eq!(converted_event.sender(), "@carl:example.com"); - } - #[test] fn old_verification_state_to_new_migration() { #[derive(Deserialize)] diff --git a/crates/matrix-sdk-ui/src/notification_client.rs b/crates/matrix-sdk-ui/src/notification_client.rs index 5af760e71..712f543c7 100644 --- a/crates/matrix-sdk-ui/src/notification_client.rs +++ b/crates/matrix-sdk-ui/src/notification_client.rs @@ -20,7 +20,7 @@ use std::{ use futures_util::{pin_mut, StreamExt as _}; use matrix_sdk::{room::Room, Client, ClientBuildError, SlidingSyncList, SlidingSyncMode}; use matrix_sdk_base::{ - deserialized_responses::TimelineEvent, sliding_sync::http, RoomState, StoreError, + deserialized_responses::SyncTimelineEvent, sliding_sync::http, RoomState, StoreError, }; use ruma::{ assign, @@ -159,7 +159,7 @@ impl NotificationClient { &self, room: &Room, raw_event: &Raw, - ) -> Result, Error> { + ) -> Result, Error> { let event: AnySyncTimelineEvent = raw_event.deserialize().map_err(|_| Error::InvalidRumaEvent)?; @@ -507,9 +507,9 @@ impl NotificationClient { if let Some(mut timeline_event) = self.retry_decryption(&room, timeline_event).await? { - let push_actions = timeline_event.push_actions.take(); + let push_actions = std::mem::take(&mut timeline_event.push_actions); raw_event = RawNotificationEvent::Timeline(timeline_event.into_raw()); - push_actions + Some(push_actions) } else { room.event_push_actions(timeline_event).await? } @@ -564,18 +564,19 @@ impl NotificationClient { timeline_event = decrypted_event; } - if let Some(actions) = timeline_event.push_actions.as_ref() { - if !actions.iter().any(|a| a.should_notify()) { - return Ok(None); - } + // TODO: nope + if !timeline_event.push_actions.is_empty() + && !timeline_event.push_actions.iter().any(|a| a.should_notify()) + { + return Ok(None); } - let push_actions = timeline_event.push_actions.take(); + let push_actions = std::mem::take(&mut timeline_event.push_actions); Ok(Some( NotificationItem::new( &room, RawNotificationEvent::Timeline(timeline_event.into_raw()), - push_actions.as_deref(), + Some(&push_actions), state_events, ) .await?, diff --git a/crates/matrix-sdk-ui/src/timeline/controller/state.rs b/crates/matrix-sdk-ui/src/timeline/controller/state.rs index 7c6b3e527..1d7a8b814 100644 --- a/crates/matrix-sdk-ui/src/timeline/controller/state.rs +++ b/crates/matrix-sdk-ui/src/timeline/controller/state.rs @@ -24,7 +24,6 @@ use itertools::Itertools as _; use matrix_sdk::{ deserialized_responses::SyncTimelineEvent, ring_buffer::RingBuffer, send_queue::SendHandle, }; -use matrix_sdk_base::deserialized_responses::TimelineEvent; #[cfg(test)] use ruma::events::receipt::ReceiptEventContent; use ruma::{ @@ -257,7 +256,7 @@ impl TimelineState { room_data_provider: &P, settings: &TimelineSettings, ) where - Fut: Future>, + Fut: Future>, { let mut txn = self.transaction(); @@ -274,9 +273,12 @@ impl TimelineState { continue; }; - event.push_actions = push_rules_context.as_ref().map(|(push_rules, push_context)| { - push_rules.get_actions(event.raw(), push_context).to_owned() - }); + event.push_actions = push_rules_context + .as_ref() + .map(|(push_rules, push_context)| { + push_rules.get_actions(event.raw(), push_context).to_owned() + }) + .unwrap_or_default(); let handle_one_res = txn .handle_remote_event( diff --git a/crates/matrix-sdk-ui/src/timeline/event_item/content/message.rs b/crates/matrix-sdk-ui/src/timeline/event_item/content/message.rs index 09ee071f9..06c715eaf 100644 --- a/crates/matrix-sdk-ui/src/timeline/event_item/content/message.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_item/content/message.rs @@ -17,7 +17,7 @@ use std::{fmt, sync::Arc}; use imbl::{vector, Vector}; -use matrix_sdk::{deserialized_responses::TimelineEvent, Room}; +use matrix_sdk::{deserialized_responses::SyncTimelineEvent, Room}; use ruma::{ assign, events::{ @@ -340,14 +340,14 @@ impl RepliedToEvent { /// Try to create a `RepliedToEvent` from a `TimelineEvent` by providing the /// room. pub async fn try_from_timeline_event_for_room( - timeline_event: TimelineEvent, + timeline_event: SyncTimelineEvent, room_data_provider: &Room, ) -> Result { Self::try_from_timeline_event(timeline_event, room_data_provider).await } pub(in crate::timeline) async fn try_from_timeline_event( - timeline_event: TimelineEvent, + timeline_event: SyncTimelineEvent, room_data_provider: &P, ) -> Result { let event = match timeline_event.raw().deserialize() { diff --git a/crates/matrix-sdk-ui/src/timeline/tests/mod.rs b/crates/matrix-sdk-ui/src/timeline/tests/mod.rs index 23bab244a..61aa0ca9f 100644 --- a/crates/matrix-sdk-ui/src/timeline/tests/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/tests/mod.rs @@ -27,7 +27,7 @@ use futures_core::Stream; use indexmap::IndexMap; use matrix_sdk::{ config::RequestConfig, - deserialized_responses::{SyncTimelineEvent, TimelineEvent}, + deserialized_responses::SyncTimelineEvent, event_cache::paginator::{PaginableRoom, PaginatorError}, room::{EventWithContextResponse, Messages, MessagesOptions}, send_queue::RoomSendQueueUpdate, @@ -196,7 +196,7 @@ impl TestTimeline { } async fn handle_back_paginated_event(&self, event: Raw) { - let timeline_event = TimelineEvent::new(event.cast()); + let timeline_event = SyncTimelineEvent::new(event.cast()); self.controller .add_events_at( [timeline_event].into_iter(), diff --git a/crates/matrix-sdk-ui/src/timeline/traits.rs b/crates/matrix-sdk-ui/src/timeline/traits.rs index 55cae54db..912b1fd0b 100644 --- a/crates/matrix-sdk-ui/src/timeline/traits.rs +++ b/crates/matrix-sdk-ui/src/timeline/traits.rs @@ -19,7 +19,7 @@ use indexmap::IndexMap; #[cfg(test)] use matrix_sdk::crypto::{DecryptionSettings, RoomEventDecryptionResult, TrustRequirement}; use matrix_sdk::{ - crypto::types::events::CryptoContextInfo, deserialized_responses::TimelineEvent, + crypto::types::events::CryptoContextInfo, deserialized_responses::SyncTimelineEvent, event_cache::paginator::PaginableRoom, AsyncTraitDeps, Result, Room, SendOutsideWasm, }; use matrix_sdk_base::{latest_event::LatestEvent, RoomInfo}; @@ -281,18 +281,24 @@ pub(super) trait Decryptor: AsyncTraitDeps + Clone + 'static { fn decrypt_event_impl( &self, raw: &Raw, - ) -> impl Future> + SendOutsideWasm; + ) -> impl Future> + SendOutsideWasm; } impl Decryptor for Room { - async fn decrypt_event_impl(&self, raw: &Raw) -> Result { + async fn decrypt_event_impl( + &self, + raw: &Raw, + ) -> Result { self.decrypt_event(raw.cast_ref()).await } } #[cfg(test)] impl Decryptor for (matrix_sdk_base::crypto::OlmMachine, ruma::OwnedRoomId) { - async fn decrypt_event_impl(&self, raw: &Raw) -> Result { + async fn decrypt_event_impl( + &self, + raw: &Raw, + ) -> Result { let (olm_machine, room_id) = self; let decryption_settings = DecryptionSettings { sender_device_trust_requirement: TrustRequirement::Untrusted }; @@ -302,7 +308,7 @@ impl Decryptor for (matrix_sdk_base::crypto::OlmMachine, ruma::OwnedRoomId) { { RoomEventDecryptionResult::Decrypted(decrypted) => Ok(decrypted.into()), RoomEventDecryptionResult::UnableToDecrypt(utd_info) => { - Ok(TimelineEvent::new_utd_event(raw.clone(), utd_info)) + Ok(SyncTimelineEvent::new_utd_event(raw.clone(), utd_info)) } } } diff --git a/crates/matrix-sdk-ui/tests/integration/main.rs b/crates/matrix-sdk-ui/tests/integration/main.rs index 9eb3b40e1..e59f31128 100644 --- a/crates/matrix-sdk-ui/tests/integration/main.rs +++ b/crates/matrix-sdk-ui/tests/integration/main.rs @@ -13,7 +13,7 @@ // limitations under the License. use itertools::Itertools as _; -use matrix_sdk::deserialized_responses::TimelineEvent; +use matrix_sdk::deserialized_responses::SyncTimelineEvent; use ruma::{events::AnyStateEvent, serde::Raw, EventId, RoomId}; use serde::Serialize; use serde_json::json; @@ -55,15 +55,16 @@ async fn mock_sync(server: &MockServer, response_body: impl Serialize, since: Op /// /// Note: pass `events_before` in the normal order, I'll revert the order for /// you. +// TODO: replace with MatrixMockServer #[allow(clippy::too_many_arguments)] // clippy you've got such a fixed mindset async fn mock_context( server: &MockServer, room_id: &RoomId, event_id: &EventId, prev_batch_token: Option, - events_before: Vec, - event: TimelineEvent, - events_after: Vec, + events_before: Vec, + event: SyncTimelineEvent, + events_after: Vec, next_batch_token: Option, state: Vec>, ) { @@ -86,11 +87,12 @@ async fn mock_context( /// /// Note: pass `chunk` in the correct order: topological for forward pagination, /// reverse topological for backwards pagination. +// TODO: replace with MatrixMockServer async fn mock_messages( server: &MockServer, start: String, end: Option, - chunk: Vec, + chunk: Vec, state: Vec>, ) { Mock::given(method("GET")) diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/focus_event.rs b/crates/matrix-sdk-ui/tests/integration/timeline/focus_event.rs index f4162d429..ee7a3b3e6 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/focus_event.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/focus_event.rs @@ -54,13 +54,13 @@ async fn test_new_focused() { target_event, Some("prev1".to_owned()), vec![ - f.text_msg("i tried so hard").sender(*ALICE).into_timeline(), - f.text_msg("and got so far").sender(*ALICE).into_timeline(), + f.text_msg("i tried so hard").sender(*ALICE).into_sync(), + f.text_msg("and got so far").sender(*ALICE).into_sync(), ], - f.text_msg("in the end").event_id(target_event).sender(*BOB).into_timeline(), + f.text_msg("in the end").event_id(target_event).sender(*BOB).into_sync(), vec![ - f.text_msg("it doesn't even").sender(*ALICE).into_timeline(), - f.text_msg("matter").sender(*ALICE).into_timeline(), + f.text_msg("it doesn't even").sender(*ALICE).into_sync(), + f.text_msg("matter").sender(*ALICE).into_sync(), ], Some("next1".to_owned()), vec![], @@ -114,8 +114,8 @@ async fn test_new_focused() { None, vec![ // reversed manually here - f.text_msg("And even though I tried, it all fell apart").sender(*BOB).into_timeline(), - f.text_msg("I kept everything inside").sender(*BOB).into_timeline(), + f.text_msg("And even though I tried, it all fell apart").sender(*BOB).into_sync(), + f.text_msg("I kept everything inside").sender(*BOB).into_sync(), ], vec![], ) @@ -154,8 +154,8 @@ async fn test_new_focused() { "next1".to_owned(), Some("next2".to_owned()), vec![ - f.text_msg("I had to fall, to lose it all").sender(*BOB).into_timeline(), - f.text_msg("But in the end, it doesn't event matter").sender(*BOB).into_timeline(), + f.text_msg("I had to fall, to lose it all").sender(*BOB).into_sync(), + f.text_msg("But in the end, it doesn't event matter").sender(*BOB).into_sync(), ], vec![], ) @@ -208,7 +208,7 @@ async fn test_focused_timeline_reacts() { target_event, None, vec![], - f.text_msg("yolo").event_id(target_event).sender(*BOB).into_timeline(), + f.text_msg("yolo").event_id(target_event).sender(*BOB).into_sync(), vec![], None, vec![], @@ -293,7 +293,7 @@ async fn test_focused_timeline_local_echoes() { target_event, None, vec![], - f.text_msg("yolo").event_id(target_event).sender(*BOB).into_timeline(), + f.text_msg("yolo").event_id(target_event).sender(*BOB).into_sync(), vec![], None, vec![], @@ -372,7 +372,7 @@ async fn test_focused_timeline_doesnt_show_local_echoes() { target_event, None, vec![], - f.text_msg("yolo").event_id(target_event).sender(*BOB).into_timeline(), + f.text_msg("yolo").event_id(target_event).sender(*BOB).into_sync(), vec![], None, vec![], diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/pinned_event.rs b/crates/matrix-sdk-ui/tests/integration/timeline/pinned_event.rs index 943d6d5f5..d7c57621e 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/pinned_event.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/pinned_event.rs @@ -13,7 +13,7 @@ use matrix_sdk::{ }, Client, Room, }; -use matrix_sdk_base::deserialized_responses::TimelineEvent; +use matrix_sdk_base::deserialized_responses::SyncTimelineEvent; use matrix_sdk_test::{ async_test, event_factory::EventFactory, JoinedRoomBuilder, StateTestEvent, SyncResponseBuilder, BOB, @@ -880,7 +880,7 @@ async fn mock_events_endpoint( .mock_room_event() .room(room_id.to_owned()) .match_event_id() - .ok(TimelineEvent::new(event.cast())) + .ok(SyncTimelineEvent::new(event.cast())) .mount() .await; } diff --git a/crates/matrix-sdk/src/event_cache/mod.rs b/crates/matrix-sdk/src/event_cache/mod.rs index 0ea2fa05b..ddc29a85b 100644 --- a/crates/matrix-sdk/src/event_cache/mod.rs +++ b/crates/matrix-sdk/src/event_cache/mod.rs @@ -36,7 +36,7 @@ use std::{ use eyeball::Subscriber; use eyeball_im::VectorDiff; use matrix_sdk_base::{ - deserialized_responses::{AmbiguityChange, SyncTimelineEvent, TimelineEvent}, + deserialized_responses::{AmbiguityChange, SyncTimelineEvent}, event_cache::store::{EventCacheStoreError, EventCacheStoreLock}, store_locks::LockStoreError, sync::RoomUpdates, @@ -662,7 +662,7 @@ pub struct BackPaginationOutcome { /// technically, the last one in the topological ordering). /// /// Note: they're not deduplicated (TODO: smart reconciliation). - pub events: Vec, + pub events: Vec, } /// An update related to events happened in a room. diff --git a/crates/matrix-sdk/src/event_cache/paginator.rs b/crates/matrix-sdk/src/event_cache/paginator.rs index 4df79363e..e3bd46301 100644 --- a/crates/matrix-sdk/src/event_cache/paginator.rs +++ b/crates/matrix-sdk/src/event_cache/paginator.rs @@ -20,7 +20,9 @@ use std::{future::Future, sync::Mutex}; use eyeball::{SharedObservable, Subscriber}; -use matrix_sdk_base::{deserialized_responses::TimelineEvent, SendOutsideWasm, SyncOutsideWasm}; +use matrix_sdk_base::{ + deserialized_responses::SyncTimelineEvent, SendOutsideWasm, SyncOutsideWasm, +}; use ruma::{api::Direction, EventId, OwnedEventId, UInt}; use super::pagination::PaginationToken; @@ -116,7 +118,7 @@ pub struct PaginationResult { /// /// If this is the result of a forward pagination, then the events are in /// topological order. - pub events: Vec, + pub events: Vec, /// Did we hit *an* end of the timeline? /// @@ -132,7 +134,7 @@ pub struct PaginationResult { #[derive(Debug)] pub struct StartFromResult { /// All the events returned during this pagination, in topological ordering. - pub events: Vec, + pub events: Vec, /// Whether the /context query returned a previous batch token. pub has_prev: bool, @@ -536,7 +538,7 @@ mod tests { use assert_matches2::assert_let; use futures_core::Future; use futures_util::FutureExt as _; - use matrix_sdk_base::deserialized_responses::TimelineEvent; + use matrix_sdk_base::deserialized_responses::SyncTimelineEvent; use matrix_sdk_test::{async_test, event_factory::EventFactory}; use once_cell::sync::Lazy; use ruma::{api::Direction, event_id, room_id, uint, user_id, EventId, RoomId, UInt, UserId}; @@ -559,8 +561,8 @@ mod tests { wait_for_ready: bool, target_event_text: Arc>, - next_events: Arc>>, - prev_events: Arc>>, + next_events: Arc>>, + prev_events: Arc>>, prev_batch_token: Arc>>, next_batch_token: Arc>>, @@ -609,7 +611,7 @@ mod tests { .event_factory .text_msg(self.target_event_text.lock().await.clone()) .event_id(event_id) - .into_timeline(); + .into_sync(); // Properly simulate `num_events`: take either the closest num_events events // before, or use all of the before events and then consume after events. @@ -707,17 +709,10 @@ mod tests { *room.target_event_text.lock().await = "fetch_from".to_owned(); *room.prev_events.lock().await = (0..10) .rev() - .map(|i| { - TimelineEvent::new( - event_factory.text_msg(format!("before-{i}")).into_raw_timeline(), - ) - }) - .collect(); - *room.next_events.lock().await = (0..10) - .map(|i| { - TimelineEvent::new(event_factory.text_msg(format!("after-{i}")).into_raw_timeline()) - }) + .map(|i| event_factory.text_msg(format!("before-{i}")).into_sync()) .collect(); + *room.next_events.lock().await = + (0..10).map(|i| event_factory.text_msg(format!("after-{i}")).into_sync()).collect(); // When I call `Paginator::start_from`, it works, let paginator = Arc::new(Paginator::new(room.clone())); @@ -753,12 +748,8 @@ mod tests { let event_factory = &room.event_factory; *room.target_event_text.lock().await = "fetch_from".to_owned(); - *room.prev_events.lock().await = (0..100) - .rev() - .map(|i| { - TimelineEvent::new(event_factory.text_msg(format!("ev{i}")).into_raw_timeline()) - }) - .collect(); + *room.prev_events.lock().await = + (0..100).rev().map(|i| event_factory.text_msg(format!("ev{i}")).into_sync()).collect(); // When I call `Paginator::start_from`, it works, let paginator = Arc::new(Paginator::new(room.clone())); @@ -811,7 +802,7 @@ mod tests { assert!(paginator.hit_timeline_end()); // Preparing data for the next back-pagination. - *room.prev_events.lock().await = vec![event_factory.text_msg("previous").into_timeline()]; + *room.prev_events.lock().await = vec![event_factory.text_msg("previous").into_sync()]; *room.prev_batch_token.lock().await = Some("prev2".to_owned()); // When I backpaginate, I get the events I expect. @@ -824,7 +815,7 @@ mod tests { // And I can backpaginate again, because there's a prev batch token // still. - *room.prev_events.lock().await = vec![event_factory.text_msg("oldest").into_timeline()]; + *room.prev_events.lock().await = vec![event_factory.text_msg("oldest").into_sync()]; *room.prev_batch_token.lock().await = None; let prev = paginator @@ -875,9 +866,7 @@ mod tests { // Preparing data for the next back-pagination. *room.prev_events.lock().await = (0..100) .rev() - .map(|i| { - TimelineEvent::new(event_factory.text_msg(format!("prev{i}")).into_raw_timeline()) - }) + .map(|i| event_factory.text_msg(format!("prev{i}")).into_sync()) .collect(); *room.prev_batch_token.lock().await = None; @@ -927,7 +916,7 @@ mod tests { assert!(!paginator.hit_timeline_end()); // Preparing data for the next forward-pagination. - *room.next_events.lock().await = vec![event_factory.text_msg("next").into_timeline()]; + *room.next_events.lock().await = vec![event_factory.text_msg("next").into_sync()]; *room.next_batch_token.lock().await = Some("next2".to_owned()); // When I forward-paginate, I get the events I expect. @@ -940,7 +929,7 @@ mod tests { // And I can forward-paginate again, because there's a prev batch token // still. - *room.next_events.lock().await = vec![event_factory.text_msg("latest").into_timeline()]; + *room.next_events.lock().await = vec![event_factory.text_msg("latest").into_sync()]; *room.next_batch_token.lock().await = None; let next = paginator diff --git a/crates/matrix-sdk/src/room/messages.rs b/crates/matrix-sdk/src/room/messages.rs index 7c4709feb..37d2e6b38 100644 --- a/crates/matrix-sdk/src/room/messages.rs +++ b/crates/matrix-sdk/src/room/messages.rs @@ -14,7 +14,7 @@ use std::fmt; -use matrix_sdk_common::{debug::DebugStructExt as _, deserialized_responses::TimelineEvent}; +use matrix_sdk_common::{debug::DebugStructExt as _, deserialized_responses::SyncTimelineEvent}; use ruma::{ api::{ client::{filter::RoomEventFilter, message::get_message_events}, @@ -134,7 +134,7 @@ pub struct Messages { pub end: Option, /// A list of room events. - pub chunk: Vec, + pub chunk: Vec, /// A list of state events relevant to showing the `chunk`. pub state: Vec>, @@ -148,19 +148,19 @@ pub struct Messages { #[derive(Debug, Default)] pub struct EventWithContextResponse { /// The event targeted by the /context query. - pub event: Option, + pub event: Option, /// Events before the target event, if a non-zero context size was /// requested. /// /// Like the corresponding Ruma response, these are in reverse chronological /// order. - pub events_before: Vec, + pub events_before: Vec, /// Events after the target event, if a non-zero context size was requested. /// /// Like the corresponding Ruma response, these are in chronological order. - pub events_after: Vec, + pub events_after: Vec, /// Token to paginate backwards, aka "start" token. pub prev_batch_token: Option, diff --git a/crates/matrix-sdk/src/room/mod.rs b/crates/matrix-sdk/src/room/mod.rs index fbe5ab16b..aee4f47fd 100644 --- a/crates/matrix-sdk/src/room/mod.rs +++ b/crates/matrix-sdk/src/room/mod.rs @@ -38,7 +38,7 @@ use matrix_sdk_base::crypto::{DecryptionSettings, RoomEventDecryptionResult}; use matrix_sdk_base::crypto::{IdentityStatusChange, RoomIdentityProvider, UserIdentity}; use matrix_sdk_base::{ deserialized_responses::{ - RawAnySyncOrStrippedState, RawSyncOrStrippedState, SyncOrStrippedState, TimelineEvent, + RawAnySyncOrStrippedState, RawSyncOrStrippedState, SyncOrStrippedState, }, media::MediaThumbnailSettings, store::StateStoreExt, @@ -341,10 +341,10 @@ impl Room { if let Ok(event) = self.decrypt_event(event.cast_ref()).await { event } else { - TimelineEvent::new(event) + SyncTimelineEvent::new(event.cast()) } } else { - TimelineEvent::new(event) + SyncTimelineEvent::new(event.cast()) }; response.chunk.push(decrypted_event); } @@ -353,8 +353,7 @@ impl Room { let push_rules = self.client().account().push_rules().await?; for event in &mut response.chunk { - event.push_actions = - Some(push_rules.get_actions(event.raw(), &push_context).to_owned()); + event.push_actions = push_rules.get_actions(event.raw(), &push_context).to_owned(); } } @@ -447,7 +446,7 @@ impl Room { /// /// Doesn't return an error `Result` when decryption failed; only logs from /// the crypto crate will indicate so. - async fn try_decrypt_event(&self, event: Raw) -> Result { + async fn try_decrypt_event(&self, event: Raw) -> Result { #[cfg(feature = "e2e-encryption")] if let Ok(AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomEncrypted( SyncMessageLikeEvent::Original(_), @@ -458,8 +457,8 @@ impl Room { } } - let mut event = TimelineEvent::new(event); - event.push_actions = self.event_push_actions(event.raw()).await?; + let mut event = SyncTimelineEvent::new(event.cast()); + event.push_actions = self.event_push_actions(event.raw()).await?.unwrap_or_default(); Ok(event) } @@ -472,7 +471,7 @@ impl Room { &self, event_id: &EventId, request_config: Option, - ) -> Result { + ) -> Result { let request = get_room_event::v3::Request::new(self.room_id().to_owned(), event_id.to_owned()); @@ -1278,14 +1277,14 @@ impl Room { pub async fn decrypt_event( &self, event: &Raw, - ) -> Result { + ) -> Result { let machine = self.client.olm_machine().await; let machine = machine.as_ref().ok_or(Error::NoOlmMachine)?; let decryption_settings = DecryptionSettings { sender_device_trust_requirement: self.client.base_client().decryption_trust_requirement, }; - let mut event: TimelineEvent = match machine + let mut event: SyncTimelineEvent = match machine .try_decrypt_room_event(event.cast_ref(), self.inner.room_id(), &decryption_settings) .await? { @@ -1295,11 +1294,11 @@ impl Room { .encryption() .backups() .maybe_download_room_key(self.room_id().to_owned(), event.clone()); - TimelineEvent::new_utd_event(event.clone().cast(), utd_info) + SyncTimelineEvent::new_utd_event(event.clone().cast(), utd_info) } }; - event.push_actions = self.event_push_actions(event.raw()).await?; + event.push_actions = self.event_push_actions(event.raw()).await?.unwrap_or_default(); Ok(event) } diff --git a/crates/matrix-sdk/src/sliding_sync/room.rs b/crates/matrix-sdk/src/sliding_sync/room.rs index b78cdc147..92e83f581 100644 --- a/crates/matrix-sdk/src/sliding_sync/room.rs +++ b/crates/matrix-sdk/src/sliding_sync/room.rs @@ -214,7 +214,6 @@ impl From<&SlidingSyncRoom> for FrozenSlidingSyncRoom { #[cfg(test)] mod tests { use imbl::vector; - use matrix_sdk_base::deserialized_responses::TimelineEvent; use matrix_sdk_common::deserialized_responses::SyncTimelineEvent; use matrix_sdk_test::async_test; use ruma::{events::room::message::RoomMessageEventContent, room_id, serde::Raw, RoomId}; @@ -315,7 +314,7 @@ mod tests { macro_rules! timeline_event { (from $sender:literal with id $event_id:literal at $ts:literal: $message:literal) => { - TimelineEvent::new( + SyncTimelineEvent::new( Raw::new(&json!({ "content": RoomMessageEventContent::text_plain($message), "type": "m.room.message", @@ -606,7 +605,7 @@ mod tests { let frozen_room = FrozenSlidingSyncRoom { room_id: room_id!("!29fhd83h92h0:example.com").to_owned(), prev_batch: Some("foo".to_owned()), - timeline_queue: vector![TimelineEvent::new( + timeline_queue: vector![SyncTimelineEvent::new( Raw::new(&json!({ "content": RoomMessageEventContent::text_plain("let it gooo!"), "type": "m.room.message", @@ -658,7 +657,7 @@ mod tests { let max = NUMBER_OF_TIMELINE_EVENTS_TO_KEEP_FOR_THE_CACHE - 1; let timeline_events = (0..=max) .map(|nth| { - TimelineEvent::new( + SyncTimelineEvent::new( Raw::new(&json!({ "content": RoomMessageEventContent::text_plain(format!("message {nth}")), "type": "m.room.message", @@ -695,7 +694,7 @@ mod tests { let max = NUMBER_OF_TIMELINE_EVENTS_TO_KEEP_FOR_THE_CACHE + 2; let timeline_events = (0..=max) .map(|nth| { - TimelineEvent::new( + SyncTimelineEvent::new( Raw::new(&json!({ "content": RoomMessageEventContent::text_plain(format!("message {nth}")), "type": "m.room.message", diff --git a/crates/matrix-sdk/src/test_utils/mocks.rs b/crates/matrix-sdk/src/test_utils/mocks.rs index 34d232b54..44dda8cf1 100644 --- a/crates/matrix-sdk/src/test_utils/mocks.rs +++ b/crates/matrix-sdk/src/test_utils/mocks.rs @@ -22,7 +22,7 @@ use std::{ sync::{Arc, Mutex}, }; -use matrix_sdk_base::deserialized_responses::TimelineEvent; +use matrix_sdk_base::deserialized_responses::SyncTimelineEvent; use matrix_sdk_test::{ test_json, InvitedRoomBuilder, JoinedRoomBuilder, KnockedRoomBuilder, LeftRoomBuilder, SyncResponseBuilder, @@ -1856,7 +1856,7 @@ impl<'a> MockEndpoint<'a, RoomEventEndpoint> { /// Returns a redact endpoint that emulates success, i.e. the redaction /// event has been sent with the given event id. - pub fn ok(self, event: TimelineEvent) -> MatrixMock<'a> { + pub fn ok(self, event: SyncTimelineEvent) -> MatrixMock<'a> { let event_path = if self.endpoint.match_event_id { let event_id = event.kind.event_id().expect("an event id is required"); // The event id should begin with `$`, which would be taken as the end of the diff --git a/crates/matrix-sdk/tests/integration/room/common.rs b/crates/matrix-sdk/tests/integration/room/common.rs index a61a4bc76..fbe03af14 100644 --- a/crates/matrix-sdk/tests/integration/room/common.rs +++ b/crates/matrix-sdk/tests/integration/room/common.rs @@ -667,7 +667,7 @@ async fn test_event() { ); assert_eq!(event.event_id(), event_id); - let push_actions = timeline_event.push_actions.unwrap(); + let push_actions = timeline_event.push_actions; assert!(push_actions.iter().any(|a| a.is_highlight())); assert!(push_actions.iter().any(|a| a.should_notify())); diff --git a/crates/matrix-sdk/tests/integration/room/joined.rs b/crates/matrix-sdk/tests/integration/room/joined.rs index 6aee69b3d..b2db9fd14 100644 --- a/crates/matrix-sdk/tests/integration/room/joined.rs +++ b/crates/matrix-sdk/tests/integration/room/joined.rs @@ -797,7 +797,7 @@ async fn test_make_reply_event_doesnt_require_event_cache() { let event_id = event_id!("$1"); let f = EventFactory::new(); mock.mock_room_event() - .ok(f.text_msg("hi").event_id(event_id).sender(&user_id).room(room_id).into_timeline()) + .ok(f.text_msg("hi").event_id(event_id).sender(&user_id).room(room_id).into_sync()) .expect(1) .named("/event") .mount() diff --git a/crates/matrix-sdk/tests/integration/send_queue.rs b/crates/matrix-sdk/tests/integration/send_queue.rs index a35fcad9d..ceb64e419 100644 --- a/crates/matrix-sdk/tests/integration/send_queue.rs +++ b/crates/matrix-sdk/tests/integration/send_queue.rs @@ -903,7 +903,7 @@ async fn test_edit() { .text_msg("msg1") .sender(client.user_id().unwrap()) .room(room_id) - .into_timeline()) + .into_sync()) .expect(1) .named("room_event") .mount() @@ -1010,7 +1010,7 @@ async fn test_edit_with_poll_start() { .poll_start("poll_start", "question", vec!["Answer A"]) .sender(client.user_id().unwrap()) .room(room_id) - .into_timeline()) + .into_sync()) .expect(1) .named("get_event") .mount() @@ -2944,7 +2944,7 @@ async fn test_update_caption_while_sending_media_event() { .image("surprise.jpeg.exe".to_owned(), owned_mxc_uri!("mxc://sdk.rs/media")) .sender(client.user_id().unwrap()) .room(room_id) - .into_timeline()) + .into_sync()) .expect(1) .named("room_event") .mount() diff --git a/testing/matrix-sdk-test/src/event_factory.rs b/testing/matrix-sdk-test/src/event_factory.rs index 48ee5afde..444e315fe 100644 --- a/testing/matrix-sdk-test/src/event_factory.rs +++ b/testing/matrix-sdk-test/src/event_factory.rs @@ -21,7 +21,7 @@ use std::{ use as_variant::as_variant; use matrix_sdk_common::deserialized_responses::{ - SyncTimelineEvent, TimelineEvent, UnableToDecryptInfo, UnableToDecryptReason, + SyncTimelineEvent, UnableToDecryptInfo, UnableToDecryptReason, }; use ruma::{ events::{ @@ -250,10 +250,6 @@ where Raw::new(&self.construct_json(true)).unwrap().cast() } - pub fn into_timeline(self) -> TimelineEvent { - TimelineEvent::new(self.into_raw_timeline()) - } - pub fn into_raw_sync(self) -> Raw { Raw::new(&self.construct_json(false)).unwrap().cast() } From 0c2046f93b546e783d162d9b730a28029ab56972 Mon Sep 17 00:00:00 2001 From: Benjamin Bouvier Date: Tue, 21 Jan 2025 15:36:57 +0100 Subject: [PATCH 09/55] refactor: have `SyncTimelineEvent::push_actions` be optional --- crates/matrix-sdk-base/src/client.rs | 2 +- .../src/event_cache/store/integration_tests.rs | 4 ++-- crates/matrix-sdk-base/src/read_receipts.rs | 6 +++++- .../src/deserialized_responses.rs | 18 ++++++++++-------- .../matrix-sdk-ui/src/notification_client.rs | 17 ++++++++--------- .../src/timeline/controller/state.rs | 13 ++++++------- crates/matrix-sdk/src/event_handler/mod.rs | 2 +- crates/matrix-sdk/src/room/mod.rs | 7 ++++--- .../tests/integration/room/common.rs | 2 +- 9 files changed, 38 insertions(+), 33 deletions(-) diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index 3bc630340..92b4c7b2d 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -535,7 +535,7 @@ impl BaseClient { }, ); } - event.push_actions = actions.to_owned(); + event.push_actions = Some(actions.to_owned()); } } Err(e) => { diff --git a/crates/matrix-sdk-base/src/event_cache/store/integration_tests.rs b/crates/matrix-sdk-base/src/event_cache/store/integration_tests.rs index 83ab9b3ed..7a5bae43c 100644 --- a/crates/matrix-sdk-base/src/event_cache/store/integration_tests.rs +++ b/crates/matrix-sdk-base/src/event_cache/store/integration_tests.rs @@ -66,7 +66,7 @@ pub fn make_test_event(room_id: &RoomId, content: &str) -> SyncTimelineEvent { encryption_info, unsigned_encryption_info: None, }), - push_actions: vec![Action::Notify], + push_actions: Some(vec![Action::Notify]), } } @@ -77,7 +77,7 @@ pub fn make_test_event(room_id: &RoomId, content: &str) -> SyncTimelineEvent { #[track_caller] pub fn check_test_event(event: &SyncTimelineEvent, text: &str) { // Check push actions. - let actions = &event.push_actions; + let actions = event.push_actions.as_ref().unwrap(); assert_eq!(actions.len(), 1); assert_matches!(&actions[0], Action::Notify); diff --git a/crates/matrix-sdk-base/src/read_receipts.rs b/crates/matrix-sdk-base/src/read_receipts.rs index 84046a9ac..bc4f54364 100644 --- a/crates/matrix-sdk-base/src/read_receipts.rs +++ b/crates/matrix-sdk-base/src/read_receipts.rs @@ -210,7 +210,11 @@ impl RoomReadReceipts { let mut has_notify = false; let mut has_mention = false; - for action in &event.push_actions { + let Some(actions) = event.push_actions.as_ref() else { + return; + }; + + for action in actions.iter() { if !has_notify && action.should_notify() { self.num_notifications += 1; has_notify = true; diff --git a/crates/matrix-sdk-common/src/deserialized_responses.rs b/crates/matrix-sdk-common/src/deserialized_responses.rs index 9156eb576..72b5ec091 100644 --- a/crates/matrix-sdk-common/src/deserialized_responses.rs +++ b/crates/matrix-sdk-common/src/deserialized_responses.rs @@ -330,8 +330,10 @@ pub struct SyncTimelineEvent { pub kind: TimelineEventKind, /// The push actions associated with this event. - #[serde(skip_serializing_if = "Vec::is_empty")] - pub push_actions: Vec, + /// + /// If it's set to `None`, then it means we couldn't compute those actions. + #[serde(skip_serializing_if = "Option::is_none")] + pub push_actions: Option>, } // See https://github.com/matrix-org/matrix-rust-sdk/pull/3749#issuecomment-2312939823. @@ -357,7 +359,7 @@ impl SyncTimelineEvent { /// This is a convenience constructor for a plaintext event when you don't /// need to set `push_action`, for example inside a test. pub fn new(event: Raw) -> Self { - Self { kind: TimelineEventKind::PlainText { event }, push_actions: vec![] } + Self { kind: TimelineEventKind::PlainText { event }, push_actions: None } } /// Create a new `SyncTimelineEvent` from the given raw event and push @@ -369,13 +371,13 @@ impl SyncTimelineEvent { event: Raw, push_actions: Vec, ) -> Self { - Self { kind: TimelineEventKind::PlainText { event }, push_actions } + Self { kind: TimelineEventKind::PlainText { event }, push_actions: Some(push_actions) } } /// Create a new `SyncTimelineEvent` to represent the given decryption /// failure. pub fn new_utd_event(event: Raw, utd_info: UnableToDecryptInfo) -> Self { - Self { kind: TimelineEventKind::UnableToDecrypt { event, utd_info }, push_actions: vec![] } + Self { kind: TimelineEventKind::UnableToDecrypt { event, utd_info }, push_actions: None } } /// Get the event id of this `SyncTimelineEvent` if the event has any valid @@ -418,7 +420,7 @@ impl SyncTimelineEvent { impl From for SyncTimelineEvent { fn from(decrypted: DecryptedRoomEvent) -> Self { - Self { kind: TimelineEventKind::Decrypted(decrypted), push_actions: Vec::new() } + Self { kind: TimelineEventKind::Decrypted(decrypted), push_actions: None } } } @@ -821,7 +823,7 @@ struct SyncTimelineEventDeserializationHelperV1 { impl From for SyncTimelineEvent { fn from(value: SyncTimelineEventDeserializationHelperV1) -> Self { let SyncTimelineEventDeserializationHelperV1 { kind, push_actions } = value; - SyncTimelineEvent { kind, push_actions } + SyncTimelineEvent { kind, push_actions: Some(push_actions) } } } @@ -873,7 +875,7 @@ impl From for SyncTimelineEvent { None => TimelineEventKind::PlainText { event }, }; - SyncTimelineEvent { kind, push_actions } + SyncTimelineEvent { kind, push_actions: Some(push_actions) } } } diff --git a/crates/matrix-sdk-ui/src/notification_client.rs b/crates/matrix-sdk-ui/src/notification_client.rs index 712f543c7..b896fa64f 100644 --- a/crates/matrix-sdk-ui/src/notification_client.rs +++ b/crates/matrix-sdk-ui/src/notification_client.rs @@ -507,9 +507,9 @@ impl NotificationClient { if let Some(mut timeline_event) = self.retry_decryption(&room, timeline_event).await? { - let push_actions = std::mem::take(&mut timeline_event.push_actions); + let push_actions = timeline_event.push_actions.take(); raw_event = RawNotificationEvent::Timeline(timeline_event.into_raw()); - Some(push_actions) + push_actions } else { room.event_push_actions(timeline_event).await? } @@ -564,19 +564,18 @@ impl NotificationClient { timeline_event = decrypted_event; } - // TODO: nope - if !timeline_event.push_actions.is_empty() - && !timeline_event.push_actions.iter().any(|a| a.should_notify()) - { - return Ok(None); + if let Some(actions) = timeline_event.push_actions.as_ref() { + if !actions.iter().any(|a| a.should_notify()) { + return Ok(None); + } } - let push_actions = std::mem::take(&mut timeline_event.push_actions); + let push_actions = timeline_event.push_actions.take(); Ok(Some( NotificationItem::new( &room, RawNotificationEvent::Timeline(timeline_event.into_raw()), - Some(&push_actions), + push_actions.as_deref(), state_events, ) .await?, diff --git a/crates/matrix-sdk-ui/src/timeline/controller/state.rs b/crates/matrix-sdk-ui/src/timeline/controller/state.rs index 1d7a8b814..5b5d1e59d 100644 --- a/crates/matrix-sdk-ui/src/timeline/controller/state.rs +++ b/crates/matrix-sdk-ui/src/timeline/controller/state.rs @@ -273,12 +273,9 @@ impl TimelineState { continue; }; - event.push_actions = push_rules_context - .as_ref() - .map(|(push_rules, push_context)| { - push_rules.get_actions(event.raw(), push_context).to_owned() - }) - .unwrap_or_default(); + event.push_actions = push_rules_context.as_ref().map(|(push_rules, push_context)| { + push_rules.get_actions(event.raw(), push_context).to_owned() + }); let handle_one_res = txn .handle_remote_event( @@ -760,7 +757,9 @@ impl TimelineStateTransaction<'_> { } else { Default::default() }, - is_highlighted: push_actions.iter().any(Action::is_highlight), + is_highlighted: push_actions + .as_ref() + .map_or(false, |actions| actions.iter().any(Action::is_highlight)), flow: Flow::Remote { event_id: event_id.clone(), raw_event: raw, diff --git a/crates/matrix-sdk/src/event_handler/mod.rs b/crates/matrix-sdk/src/event_handler/mod.rs index 5fff2db8b..4f802210b 100644 --- a/crates/matrix-sdk/src/event_handler/mod.rs +++ b/crates/matrix-sdk/src/event_handler/mod.rs @@ -402,7 +402,7 @@ impl Client { let raw_event = item.raw().json(); let encryption_info = item.encryption_info(); - let push_actions = &item.push_actions; + let push_actions = item.push_actions.as_deref().unwrap_or(&[]); // Event handlers for possibly-redacted timeline events self.call_event_handlers( diff --git a/crates/matrix-sdk/src/room/mod.rs b/crates/matrix-sdk/src/room/mod.rs index aee4f47fd..cf307f6c1 100644 --- a/crates/matrix-sdk/src/room/mod.rs +++ b/crates/matrix-sdk/src/room/mod.rs @@ -353,7 +353,8 @@ impl Room { let push_rules = self.client().account().push_rules().await?; for event in &mut response.chunk { - event.push_actions = push_rules.get_actions(event.raw(), &push_context).to_owned(); + event.push_actions = + Some(push_rules.get_actions(event.raw(), &push_context).to_owned()); } } @@ -458,7 +459,7 @@ impl Room { } let mut event = SyncTimelineEvent::new(event.cast()); - event.push_actions = self.event_push_actions(event.raw()).await?.unwrap_or_default(); + event.push_actions = self.event_push_actions(event.raw()).await?; Ok(event) } @@ -1298,7 +1299,7 @@ impl Room { } }; - event.push_actions = self.event_push_actions(event.raw()).await?.unwrap_or_default(); + event.push_actions = self.event_push_actions(event.raw()).await?; Ok(event) } diff --git a/crates/matrix-sdk/tests/integration/room/common.rs b/crates/matrix-sdk/tests/integration/room/common.rs index fbe03af14..a61a4bc76 100644 --- a/crates/matrix-sdk/tests/integration/room/common.rs +++ b/crates/matrix-sdk/tests/integration/room/common.rs @@ -667,7 +667,7 @@ async fn test_event() { ); assert_eq!(event.event_id(), event_id); - let push_actions = timeline_event.push_actions; + let push_actions = timeline_event.push_actions.unwrap(); assert!(push_actions.iter().any(|a| a.is_highlight())); assert!(push_actions.iter().any(|a| a.should_notify())); From 3428494468dc0b34397d74c976e05fcb5ede9284 Mon Sep 17 00:00:00 2001 From: Benjamin Bouvier Date: Tue, 21 Jan 2025 15:44:46 +0100 Subject: [PATCH 10/55] refactor: rename `SyncTimelineEvent` to `TimelineEvent` --- crates/matrix-sdk-base/src/client.rs | 14 ++-- crates/matrix-sdk-base/src/event_cache/mod.rs | 4 +- .../event_cache/store/integration_tests.rs | 8 +-- crates/matrix-sdk-base/src/latest_event.rs | 22 +++--- crates/matrix-sdk-base/src/read_receipts.rs | 34 ++++----- crates/matrix-sdk-base/src/rooms/normal.rs | 6 +- .../matrix-sdk-base/src/sliding_sync/mod.rs | 22 +++--- .../src/store/migration_helpers.rs | 4 +- crates/matrix-sdk-base/src/sync.rs | 4 +- .../src/deserialized_responses.rs | 69 +++++++++---------- .../event_cache_store/003_events.sql | 2 +- .../matrix-sdk-ui/src/notification_client.rs | 4 +- .../src/timeline/controller/mod.rs | 10 +-- .../src/timeline/controller/state.rs | 18 ++--- .../src/timeline/event_handler.rs | 2 +- .../timeline/event_item/content/message.rs | 6 +- .../src/timeline/event_item/mod.rs | 15 ++-- .../src/timeline/pinned_events_loader.rs | 14 ++-- .../matrix-sdk-ui/src/timeline/tests/basic.rs | 4 +- .../matrix-sdk-ui/src/timeline/tests/edit.rs | 6 +- .../src/timeline/tests/encryption.rs | 6 +- .../src/timeline/tests/event_filter.rs | 6 +- .../src/timeline/tests/invalid.rs | 8 +-- .../matrix-sdk-ui/src/timeline/tests/mod.rs | 8 +-- .../src/timeline/tests/reactions.rs | 4 +- .../src/timeline/tests/shields.rs | 4 +- crates/matrix-sdk-ui/src/timeline/traits.rs | 16 ++--- .../matrix-sdk-ui/tests/integration/main.rs | 10 +-- .../integration/timeline/pinned_event.rs | 4 +- .../src/event_cache/deduplicator.rs | 4 +- crates/matrix-sdk/src/event_cache/mod.rs | 18 ++--- .../matrix-sdk/src/event_cache/pagination.rs | 4 +- .../matrix-sdk/src/event_cache/paginator.rs | 14 ++-- crates/matrix-sdk/src/event_cache/room/mod.rs | 34 +++++---- crates/matrix-sdk/src/event_handler/mod.rs | 4 +- crates/matrix-sdk/src/room/edit.rs | 14 ++-- crates/matrix-sdk/src/room/messages.rs | 10 +-- crates/matrix-sdk/src/room/mod.rs | 20 +++--- crates/matrix-sdk/src/sliding_sync/client.rs | 2 +- crates/matrix-sdk/src/sliding_sync/mod.rs | 10 +-- crates/matrix-sdk/src/sliding_sync/room.rs | 24 +++---- crates/matrix-sdk/src/test_utils/mocks.rs | 4 +- crates/matrix-sdk/src/test_utils/mod.rs | 6 +- .../tests/integration/event_cache.rs | 4 +- testing/matrix-sdk-test/src/event_factory.rs | 14 ++-- 45 files changed, 256 insertions(+), 264 deletions(-) diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index 92b4c7b2d..4a910a3ae 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -68,7 +68,7 @@ use crate::latest_event::{is_suitable_for_latest_event, LatestEvent, PossibleLat #[cfg(feature = "e2e-encryption")] use crate::RoomMemberships; use crate::{ - deserialized_responses::{DisplayName, RawAnySyncOrStrippedTimelineEvent, SyncTimelineEvent}, + deserialized_responses::{DisplayName, RawAnySyncOrStrippedTimelineEvent, TimelineEvent}, error::{Error, Result}, event_cache::store::EventCacheStoreLock, response_processors::AccountDataProcessor, @@ -347,9 +347,9 @@ impl BaseClient { Ok(()) } - /// Attempt to decrypt the given raw event into a `SyncTimelineEvent`. + /// Attempt to decrypt the given raw event into a [`TimelineEvent`]. /// - /// In the case of a decryption error, returns a `SyncTimelineEvent` + /// In the case of a decryption error, returns a [`TimelineEvent`] /// representing the decryption error; in the case of problems with our /// application, returns `Err`. /// @@ -359,7 +359,7 @@ impl BaseClient { &self, event: &Raw, room_id: &RoomId, - ) -> Result> { + ) -> Result> { let olm = self.olm_machine().await; let Some(olm) = olm.as_ref() else { return Ok(None) }; @@ -372,7 +372,7 @@ impl BaseClient { .await? { RoomEventDecryptionResult::Decrypted(decrypted) => { - let event: SyncTimelineEvent = decrypted.into(); + let event: TimelineEvent = decrypted.into(); if let Ok(AnySyncTimelineEvent::MessageLike(e)) = event.raw().deserialize() { match &e { @@ -394,7 +394,7 @@ impl BaseClient { event } RoomEventDecryptionResult::UnableToDecrypt(utd_info) => { - SyncTimelineEvent::new_utd_event(event.clone(), utd_info) + TimelineEvent::new_utd_event(event.clone(), utd_info) } }; @@ -423,7 +423,7 @@ impl BaseClient { for raw_event in events { // Start by assuming we have a plaintext event. We'll replace it with a // decrypted or UTD event below if necessary. - let mut event = SyncTimelineEvent::new(raw_event); + let mut event = TimelineEvent::new(raw_event); match event.raw().deserialize() { Ok(e) => { diff --git a/crates/matrix-sdk-base/src/event_cache/mod.rs b/crates/matrix-sdk-base/src/event_cache/mod.rs index 0b4a80a4d..fb8f2bcf2 100644 --- a/crates/matrix-sdk-base/src/event_cache/mod.rs +++ b/crates/matrix-sdk-base/src/event_cache/mod.rs @@ -14,12 +14,12 @@ //! Event cache store and common types shared with `matrix_sdk::event_cache`. -use matrix_sdk_common::deserialized_responses::SyncTimelineEvent; +use matrix_sdk_common::deserialized_responses::TimelineEvent; pub mod store; /// The kind of event the event storage holds. -pub type Event = SyncTimelineEvent; +pub type Event = TimelineEvent; /// The kind of gap the event storage holds. #[derive(Clone, Debug)] diff --git a/crates/matrix-sdk-base/src/event_cache/store/integration_tests.rs b/crates/matrix-sdk-base/src/event_cache/store/integration_tests.rs index 7a5bae43c..f36bc662b 100644 --- a/crates/matrix-sdk-base/src/event_cache/store/integration_tests.rs +++ b/crates/matrix-sdk-base/src/event_cache/store/integration_tests.rs @@ -18,7 +18,7 @@ use assert_matches::assert_matches; use async_trait::async_trait; use matrix_sdk_common::{ deserialized_responses::{ - AlgorithmInfo, DecryptedRoomEvent, EncryptionInfo, SyncTimelineEvent, TimelineEventKind, + AlgorithmInfo, DecryptedRoomEvent, EncryptionInfo, TimelineEvent, TimelineEventKind, VerificationState, }, linked_chunk::{ @@ -42,7 +42,7 @@ use crate::{ /// correctly stores event data. /// /// Keep in sync with [`check_test_event`]. -pub fn make_test_event(room_id: &RoomId, content: &str) -> SyncTimelineEvent { +pub fn make_test_event(room_id: &RoomId, content: &str) -> TimelineEvent { let encryption_info = EncryptionInfo { sender: (*ALICE).into(), sender_device: None, @@ -60,7 +60,7 @@ pub fn make_test_event(room_id: &RoomId, content: &str) -> SyncTimelineEvent { .into_raw_timeline() .cast(); - SyncTimelineEvent { + TimelineEvent { kind: TimelineEventKind::Decrypted(DecryptedRoomEvent { event, encryption_info, @@ -75,7 +75,7 @@ pub fn make_test_event(room_id: &RoomId, content: &str) -> SyncTimelineEvent { /// /// Keep in sync with [`make_test_event`]. #[track_caller] -pub fn check_test_event(event: &SyncTimelineEvent, text: &str) { +pub fn check_test_event(event: &TimelineEvent, text: &str) { // Check push actions. let actions = event.push_actions.as_ref().unwrap(); assert_eq!(actions.len(), 1); diff --git a/crates/matrix-sdk-base/src/latest_event.rs b/crates/matrix-sdk-base/src/latest_event.rs index d610ec90f..92fc77a82 100644 --- a/crates/matrix-sdk-base/src/latest_event.rs +++ b/crates/matrix-sdk-base/src/latest_event.rs @@ -1,7 +1,7 @@ //! Utilities for working with events to decide whether they are suitable for //! use as a [crate::Room::latest_event]. -use matrix_sdk_common::deserialized_responses::SyncTimelineEvent; +use matrix_sdk_common::deserialized_responses::TimelineEvent; #[cfg(feature = "e2e-encryption")] use ruma::{ events::{ @@ -164,7 +164,7 @@ pub fn is_suitable_for_latest_event<'a>( #[derive(Clone, Debug, Serialize)] pub struct LatestEvent { /// The actual event. - event: SyncTimelineEvent, + event: TimelineEvent, /// The member profile of the event' sender. #[serde(skip_serializing_if = "Option::is_none")] @@ -178,7 +178,7 @@ pub struct LatestEvent { #[derive(Deserialize)] struct SerializedLatestEvent { /// The actual event. - event: SyncTimelineEvent, + event: TimelineEvent, /// The member profile of the event' sender. #[serde(skip_serializing_if = "Option::is_none")] @@ -211,7 +211,7 @@ impl<'de> Deserialize<'de> for LatestEvent { Err(err) => variant_errors.push(err), } - match serde_json::from_str::(raw.get()) { + match serde_json::from_str::(raw.get()) { Ok(value) => { return Ok(LatestEvent { event: value, @@ -230,13 +230,13 @@ impl<'de> Deserialize<'de> for LatestEvent { impl LatestEvent { /// Create a new [`LatestEvent`] without the sender's profile. - pub fn new(event: SyncTimelineEvent) -> Self { + pub fn new(event: TimelineEvent) -> Self { Self { event, sender_profile: None, sender_name_is_ambiguous: None } } /// Create a new [`LatestEvent`] with maybe the sender's profile. pub fn new_with_sender_details( - event: SyncTimelineEvent, + event: TimelineEvent, sender_profile: Option, sender_name_is_ambiguous: Option, ) -> Self { @@ -244,17 +244,17 @@ impl LatestEvent { } /// Transform [`Self`] into an event. - pub fn into_event(self) -> SyncTimelineEvent { + pub fn into_event(self) -> TimelineEvent { self.event } /// Get a reference to the event. - pub fn event(&self) -> &SyncTimelineEvent { + pub fn event(&self) -> &TimelineEvent { &self.event } /// Get a mutable reference to the event. - pub fn event_mut(&mut self) -> &mut SyncTimelineEvent { + pub fn event_mut(&mut self) -> &mut TimelineEvent { &mut self.event } @@ -301,7 +301,7 @@ mod tests { use assert_matches::assert_matches; #[cfg(feature = "e2e-encryption")] use assert_matches2::assert_let; - use matrix_sdk_common::deserialized_responses::SyncTimelineEvent; + use matrix_sdk_common::deserialized_responses::TimelineEvent; use ruma::serde::Raw; #[cfg(feature = "e2e-encryption")] use ruma::{ @@ -596,7 +596,7 @@ mod tests { latest_event: LatestEvent, } - let event = SyncTimelineEvent::new( + let event = TimelineEvent::new( Raw::from_json_string(json!({ "event_id": "$1" }).to_string()).unwrap(), ); diff --git a/crates/matrix-sdk-base/src/read_receipts.rs b/crates/matrix-sdk-base/src/read_receipts.rs index bc4f54364..171fd1ad3 100644 --- a/crates/matrix-sdk-base/src/read_receipts.rs +++ b/crates/matrix-sdk-base/src/read_receipts.rs @@ -123,7 +123,7 @@ use std::{ }; use eyeball_im::Vector; -use matrix_sdk_common::{deserialized_responses::SyncTimelineEvent, ring_buffer::RingBuffer}; +use matrix_sdk_common::{deserialized_responses::TimelineEvent, ring_buffer::RingBuffer}; use ruma::{ events::{ poll::{start::PollStartEventContent, unstable_start::UnstablePollStartEventContent}, @@ -202,7 +202,7 @@ impl RoomReadReceipts { /// /// Returns whether a new event triggered a new unread/notification/mention. #[inline(always)] - fn process_event(&mut self, event: &SyncTimelineEvent, user_id: &UserId) { + fn process_event(&mut self, event: &TimelineEvent, user_id: &UserId) { if marks_as_unread(event.raw(), user_id) { self.num_unread += 1; } @@ -240,7 +240,7 @@ impl RoomReadReceipts { &mut self, receipt_event_id: &EventId, user_id: &UserId, - events: impl IntoIterator, + events: impl IntoIterator, ) -> bool { let mut counting_receipts = false; @@ -273,11 +273,11 @@ impl RoomReadReceipts { pub trait PreviousEventsProvider: Send + Sync { /// Returns the list of known timeline events, in sync order, for the given /// room. - fn for_room(&self, room_id: &RoomId) -> Vector; + fn for_room(&self, room_id: &RoomId) -> Vector; } impl PreviousEventsProvider for () { - fn for_room(&self, _: &RoomId) -> Vector { + fn for_room(&self, _: &RoomId) -> Vector { Vector::new() } } @@ -296,7 +296,7 @@ struct ReceiptSelector { impl ReceiptSelector { fn new( - all_events: &Vector, + all_events: &Vector, latest_active_receipt_event: Option<&EventId>, ) -> Self { let event_id_to_pos = Self::create_sync_index(all_events.iter()); @@ -314,7 +314,7 @@ impl ReceiptSelector { /// Create a mapping of `event_id` -> sync order for all events that have an /// `event_id`. fn create_sync_index<'a>( - events: impl Iterator + 'a, + events: impl Iterator + 'a, ) -> BTreeMap { // TODO: this should be cached and incrementally updated. BTreeMap::from_iter( @@ -409,7 +409,7 @@ impl ReceiptSelector { /// Try to match an implicit receipt, that is, the one we get for events we /// sent ourselves. #[instrument(skip_all)] - fn try_match_implicit(&mut self, user_id: &UserId, new_events: &[SyncTimelineEvent]) { + fn try_match_implicit(&mut self, user_id: &UserId, new_events: &[TimelineEvent]) { for ev in new_events { // Get the `sender` field, if any, or skip this event. let Ok(Some(sender)) = ev.raw().get_field::("sender") else { continue }; @@ -436,8 +436,8 @@ impl ReceiptSelector { /// Returns true if there's an event common to both groups of events, based on /// their event id. fn events_intersects<'a>( - previous_events: impl Iterator, - new_events: &[SyncTimelineEvent], + previous_events: impl Iterator, + new_events: &[TimelineEvent], ) -> bool { let previous_events_ids = BTreeSet::from_iter(previous_events.filter_map(|ev| ev.event_id())); new_events @@ -458,8 +458,8 @@ pub(crate) fn compute_unread_counts( user_id: &UserId, room_id: &RoomId, receipt_event: Option<&ReceiptEventContent>, - previous_events: Vector, - new_events: &[SyncTimelineEvent], + previous_events: Vector, + new_events: &[TimelineEvent], read_receipts: &mut RoomReadReceipts, ) { debug!(?read_receipts, "Starting."); @@ -624,7 +624,7 @@ mod tests { use std::{num::NonZeroUsize, ops::Not as _}; use eyeball_im::Vector; - use matrix_sdk_common::{deserialized_responses::SyncTimelineEvent, ring_buffer::RingBuffer}; + use matrix_sdk_common::{deserialized_responses::TimelineEvent, ring_buffer::RingBuffer}; use matrix_sdk_test::event_factory::EventFactory; use ruma::{ event_id, @@ -724,13 +724,13 @@ mod tests { #[test] fn test_count_unread_and_mentions() { - fn make_event(user_id: &UserId, push_actions: Vec) -> SyncTimelineEvent { + fn make_event(user_id: &UserId, push_actions: Vec) -> TimelineEvent { let mut ev = EventFactory::new() .text_msg("A") .sender(user_id) .event_id(event_id!("$ida")) .into_sync(); - ev.push_actions = push_actions; + ev.push_actions = Some(push_actions); ev } @@ -805,7 +805,7 @@ mod tests { // When provided with one event, that's not the receipt event, we don't count // it. - fn make_event(event_id: &EventId) -> SyncTimelineEvent { + fn make_event(event_id: &EventId) -> TimelineEvent { EventFactory::new() .text_msg("A") .sender(user_id!("@bob:example.org")) @@ -958,7 +958,7 @@ mod tests { assert_eq!(read_receipts.num_unread, 2); } - fn make_test_events(user_id: &UserId) -> Vector { + fn make_test_events(user_id: &UserId) -> Vector { let f = EventFactory::new().sender(user_id); let ev1 = f.text_msg("With the lights out, it's less dangerous").event_id(event_id!("$1")); let ev2 = f.text_msg("Here we are now, entertain us").event_id(event_id!("$2")); diff --git a/crates/matrix-sdk-base/src/rooms/normal.rs b/crates/matrix-sdk-base/src/rooms/normal.rs index f534ad399..316d64dcc 100644 --- a/crates/matrix-sdk-base/src/rooms/normal.rs +++ b/crates/matrix-sdk-base/src/rooms/normal.rs @@ -2164,7 +2164,7 @@ mod tests { }; use assign::assign; - use matrix_sdk_common::deserialized_responses::SyncTimelineEvent; + use matrix_sdk_common::deserialized_responses::TimelineEvent; use matrix_sdk_test::{ async_test, event_factory::EventFactory, @@ -2241,7 +2241,7 @@ mod tests { last_prev_batch: Some("pb".to_owned()), sync_info: SyncInfo::FullySynced, encryption_state_synced: true, - latest_event: Some(Box::new(LatestEvent::new(SyncTimelineEvent::new( + latest_event: Some(Box::new(LatestEvent::new(TimelineEvent::new( Raw::from_json_string(json!({"sender": "@u:i.uk"}).to_string()).unwrap(), )))), base_info: Box::new( @@ -3324,7 +3324,7 @@ mod tests { #[cfg(feature = "e2e-encryption")] fn make_latest_event(event_id: &str) -> Box { - Box::new(LatestEvent::new(SyncTimelineEvent::new( + Box::new(LatestEvent::new(TimelineEvent::new( Raw::from_json_string(json!({ "event_id": event_id }).to_string()).unwrap(), ))) } diff --git a/crates/matrix-sdk-base/src/sliding_sync/mod.rs b/crates/matrix-sdk-base/src/sliding_sync/mod.rs index 29024555c..8ae81a41c 100644 --- a/crates/matrix-sdk-base/src/sliding_sync/mod.rs +++ b/crates/matrix-sdk-base/src/sliding_sync/mod.rs @@ -21,7 +21,7 @@ use std::ops::Deref; use std::{borrow::Cow, collections::BTreeMap}; #[cfg(feature = "e2e-encryption")] -use matrix_sdk_common::deserialized_responses::SyncTimelineEvent; +use matrix_sdk_common::deserialized_responses::TimelineEvent; use ruma::{ api::client::sync::sync_events::v3::{self, InvitedRoom, KnockedRoom}, events::{ @@ -690,7 +690,7 @@ impl BaseClient { async fn cache_latest_events( room: &Room, room_info: &mut RoomInfo, - events: &[SyncTimelineEvent], + events: &[TimelineEvent], changes: Option<&StateChanges>, store: Option<&Store>, ) { @@ -900,7 +900,7 @@ mod tests { use std::sync::{Arc, RwLock as SyncRwLock}; use assert_matches::assert_matches; - use matrix_sdk_common::deserialized_responses::SyncTimelineEvent; + use matrix_sdk_common::deserialized_responses::TimelineEvent; #[cfg(feature = "e2e-encryption")] use matrix_sdk_common::{ deserialized_responses::{UnableToDecryptInfo, UnableToDecryptReason}, @@ -2606,7 +2606,7 @@ mod tests { } #[cfg(feature = "e2e-encryption")] - async fn choose_event_to_cache(events: &[SyncTimelineEvent]) -> Option { + async fn choose_event_to_cache(events: &[TimelineEvent]) -> Option { let room = make_room(); let mut room_info = room.clone_info(); cache_latest_events(&room, &mut room_info, events, None, None).await; @@ -2615,11 +2615,11 @@ mod tests { } #[cfg(feature = "e2e-encryption")] - fn rawev_id(event: SyncTimelineEvent) -> String { + fn rawev_id(event: TimelineEvent) -> String { event.event_id().unwrap().to_string() } - fn ev_id(event: Option) -> String { + fn ev_id(event: Option) -> String { event.unwrap().event_id().unwrap().to_string() } @@ -2629,7 +2629,7 @@ mod tests { } #[cfg(feature = "e2e-encryption")] - fn evs_ids(events: &[SyncTimelineEvent]) -> Vec { + fn evs_ids(events: &[TimelineEvent]) -> Vec { events.iter().map(|e| e.event_id().unwrap().to_string()).collect() } @@ -2661,13 +2661,13 @@ mod tests { } #[cfg(feature = "e2e-encryption")] - fn make_event(typ: &str, id: &str) -> SyncTimelineEvent { - SyncTimelineEvent::new(make_raw_event(typ, id)) + fn make_event(typ: &str, id: &str) -> TimelineEvent { + TimelineEvent::new(make_raw_event(typ, id)) } #[cfg(feature = "e2e-encryption")] - fn make_encrypted_event(id: &str) -> SyncTimelineEvent { - SyncTimelineEvent::new_utd_event( + fn make_encrypted_event(id: &str) -> TimelineEvent { + TimelineEvent::new_utd_event( Raw::from_json_string( json!({ "type": "m.room.encrypted", diff --git a/crates/matrix-sdk-base/src/store/migration_helpers.rs b/crates/matrix-sdk-base/src/store/migration_helpers.rs index e38be0770..b22456fd1 100644 --- a/crates/matrix-sdk-base/src/store/migration_helpers.rs +++ b/crates/matrix-sdk-base/src/store/migration_helpers.rs @@ -19,7 +19,7 @@ use std::{ sync::Arc, }; -use matrix_sdk_common::deserialized_responses::SyncTimelineEvent; +use matrix_sdk_common::deserialized_responses::TimelineEvent; use ruma::{ events::{ direct::OwnedDirectUserIdentifier, @@ -76,7 +76,7 @@ pub struct RoomInfoV1 { sync_info: SyncInfo, #[serde(default = "encryption_state_default")] // see fn docs for why we use this default encryption_state_synced: bool, - latest_event: Option, + latest_event: Option, base_info: BaseRoomInfoV1, } diff --git a/crates/matrix-sdk-base/src/sync.rs b/crates/matrix-sdk-base/src/sync.rs index 824becb54..5b01e9010 100644 --- a/crates/matrix-sdk-base/src/sync.rs +++ b/crates/matrix-sdk-base/src/sync.rs @@ -16,7 +16,7 @@ use std::{collections::BTreeMap, fmt}; -use matrix_sdk_common::{debug::DebugRawEvent, deserialized_responses::SyncTimelineEvent}; +use matrix_sdk_common::{debug::DebugRawEvent, deserialized_responses::TimelineEvent}; use ruma::{ api::client::sync::sync_events::{ v3::{InvitedRoom as InvitedRoomUpdate, KnockedRoom as KnockedRoomUpdate}, @@ -236,7 +236,7 @@ pub struct Timeline { pub prev_batch: Option, /// A list of events. - pub events: Vec, + pub events: Vec, } impl Timeline { diff --git a/crates/matrix-sdk-common/src/deserialized_responses.rs b/crates/matrix-sdk-common/src/deserialized_responses.rs index 72b5ec091..54366b475 100644 --- a/crates/matrix-sdk-common/src/deserialized_responses.rs +++ b/crates/matrix-sdk-common/src/deserialized_responses.rs @@ -311,13 +311,13 @@ pub struct EncryptionInfo { // // 🚨 Note about this type, please read! 🚨 // -// `SyncTimelineEvent` is heavily used across the SDK crates. In some cases, we +// `TimelineEvent` is heavily used across the SDK crates. In some cases, we // are reaching a [`recursion_limit`] when the compiler is trying to figure out -// if `SyncTimelineEvent` implements `Sync` when it's embedded in other types. +// if `TimelineEvent` implements `Sync` when it's embedded in other types. // // We want to help the compiler so that one doesn't need to increase the // `recursion_limit`. We stop the recursive check by (un)safely implement `Sync` -// and `Send` on `SyncTimelineEvent` directly. +// and `Send` on `TimelineEvent` directly. // // See // https://github.com/matrix-org/matrix-rust-sdk/pull/3749#issuecomment-2312939823 @@ -325,7 +325,7 @@ pub struct EncryptionInfo { // // [`recursion_limit`]: https://doc.rust-lang.org/reference/attributes/limits.html#the-recursion_limit-attribute #[derive(Clone, Debug, Serialize)] -pub struct SyncTimelineEvent { +pub struct TimelineEvent { /// The event itself, together with any information on decryption. pub kind: TimelineEventKind, @@ -338,11 +338,11 @@ pub struct SyncTimelineEvent { // See https://github.com/matrix-org/matrix-rust-sdk/pull/3749#issuecomment-2312939823. #[cfg(not(feature = "test-send-sync"))] -unsafe impl Send for SyncTimelineEvent {} +unsafe impl Send for TimelineEvent {} // See https://github.com/matrix-org/matrix-rust-sdk/pull/3749#issuecomment-2312939823. #[cfg(not(feature = "test-send-sync"))] -unsafe impl Sync for SyncTimelineEvent {} +unsafe impl Sync for TimelineEvent {} #[cfg(feature = "test-send-sync")] #[test] @@ -350,11 +350,11 @@ unsafe impl Sync for SyncTimelineEvent {} fn test_send_sync_for_sync_timeline_event() { fn assert_send_sync() {} - assert_send_sync::(); + assert_send_sync::(); } -impl SyncTimelineEvent { - /// Create a new `SyncTimelineEvent` from the given raw event. +impl TimelineEvent { + /// Create a new [`TimelineEvent`] from the given raw event. /// /// This is a convenience constructor for a plaintext event when you don't /// need to set `push_action`, for example inside a test. @@ -362,7 +362,7 @@ impl SyncTimelineEvent { Self { kind: TimelineEventKind::PlainText { event }, push_actions: None } } - /// Create a new `SyncTimelineEvent` from the given raw event and push + /// Create a new [`TimelineEvent`] from the given raw event and push /// actions. /// /// This is a convenience constructor for a plaintext event, for example @@ -374,20 +374,20 @@ impl SyncTimelineEvent { Self { kind: TimelineEventKind::PlainText { event }, push_actions: Some(push_actions) } } - /// Create a new `SyncTimelineEvent` to represent the given decryption + /// Create a new [`TimelineEvent`] to represent the given decryption /// failure. pub fn new_utd_event(event: Raw, utd_info: UnableToDecryptInfo) -> Self { Self { kind: TimelineEventKind::UnableToDecrypt { event, utd_info }, push_actions: None } } - /// Get the event id of this `SyncTimelineEvent` if the event has any valid + /// Get the event id of this [`TimelineEvent`] if the event has any valid /// id. pub fn event_id(&self) -> Option { self.kind.event_id() } /// Returns a reference to the (potentially decrypted) Matrix event inside - /// this `TimelineEvent`. + /// this [`TimelineEvent`]. pub fn raw(&self) -> &Raw { self.kind.raw() } @@ -418,14 +418,14 @@ impl SyncTimelineEvent { } } -impl From for SyncTimelineEvent { +impl From for TimelineEvent { fn from(decrypted: DecryptedRoomEvent) -> Self { Self { kind: TimelineEventKind::Decrypted(decrypted), push_actions: None } } } -impl<'de> Deserialize<'de> for SyncTimelineEvent { - /// Custom deserializer for [`SyncTimelineEvent`], to support older formats. +impl<'de> Deserialize<'de> for TimelineEvent { + /// Custom deserializer for [`TimelineEvent`], to support older formats. /// /// Ideally we might use an untagged enum and then convert from that; /// however, that doesn't work due to a [serde bug](https://github.com/serde-rs/json/issues/497). @@ -446,7 +446,7 @@ impl<'de> Deserialize<'de> for SyncTimelineEvent { let v0: SyncTimelineEventDeserializationHelperV0 = serde_json::from_value(Value::Object(value)).map_err(|e| { serde::de::Error::custom(format!( - "Unable to deserialize V0-format SyncTimelineEvent: {}", + "Unable to deserialize V0-format TimelineEvent: {}", e )) })?; @@ -457,7 +457,7 @@ impl<'de> Deserialize<'de> for SyncTimelineEvent { let v1: SyncTimelineEventDeserializationHelperV1 = serde_json::from_value(Value::Object(value)).map_err(|e| { serde::de::Error::custom(format!( - "Unable to deserialize V1-format SyncTimelineEvent: {}", + "Unable to deserialize V1-format TimelineEvent: {}", e )) })?; @@ -466,8 +466,7 @@ impl<'de> Deserialize<'de> for SyncTimelineEvent { } } -/// The event within a [`TimelineEvent`] or [`SyncTimelineEvent`], together with -/// encryption data. +/// The event within a [`TimelineEvent`], together with encryption data. #[derive(Clone, Serialize, Deserialize)] pub enum TimelineEventKind { /// A successfully-decrypted encrypted event. @@ -806,9 +805,9 @@ impl fmt::Debug for PrivOwnedStr { } } -/// Deserialization helper for [`SyncTimelineEvent`], for the modern format. +/// Deserialization helper for [`TimelineEvent`], for the modern format. /// -/// This has the exact same fields as [`SyncTimelineEvent`] itself, but has a +/// This has the exact same fields as [`TimelineEvent`] itself, but has a /// regular `Deserialize` implementation. #[derive(Debug, Deserialize)] struct SyncTimelineEventDeserializationHelperV1 { @@ -820,14 +819,14 @@ struct SyncTimelineEventDeserializationHelperV1 { push_actions: Vec, } -impl From for SyncTimelineEvent { +impl From for TimelineEvent { fn from(value: SyncTimelineEventDeserializationHelperV1) -> Self { let SyncTimelineEventDeserializationHelperV1 { kind, push_actions } = value; - SyncTimelineEvent { kind, push_actions: Some(push_actions) } + TimelineEvent { kind, push_actions: Some(push_actions) } } } -/// Deserialization helper for [`SyncTimelineEvent`], for an older format. +/// Deserialization helper for [`TimelineEvent`], for an older format. #[derive(Deserialize)] struct SyncTimelineEventDeserializationHelperV0 { /// The actual event. @@ -848,7 +847,7 @@ struct SyncTimelineEventDeserializationHelperV0 { unsigned_encryption_info: Option>, } -impl From for SyncTimelineEvent { +impl From for TimelineEvent { fn from(value: SyncTimelineEventDeserializationHelperV0) -> Self { let SyncTimelineEventDeserializationHelperV0 { event, @@ -875,7 +874,7 @@ impl From for SyncTimelineEvent { None => TimelineEventKind::PlainText { event }, }; - SyncTimelineEvent { kind, push_actions: Some(push_actions) } + TimelineEvent { kind, push_actions: Some(push_actions) } } } @@ -894,7 +893,7 @@ mod tests { use super::{ AlgorithmInfo, DecryptedRoomEvent, DeviceLinkProblem, EncryptionInfo, ShieldState, - ShieldStateCode, SyncTimelineEvent, TimelineEventKind, UnableToDecryptInfo, + ShieldStateCode, TimelineEvent, TimelineEventKind, UnableToDecryptInfo, UnableToDecryptReason, UnsignedDecryptionResult, UnsignedEventLocation, VerificationLevel, VerificationState, WithheldCode, }; @@ -912,7 +911,7 @@ mod tests { #[test] fn sync_timeline_debug_content() { - let room_event = SyncTimelineEvent::new(Raw::new(&example_event()).unwrap().cast()); + let room_event = TimelineEvent::new(Raw::new(&example_event()).unwrap().cast()); let debug_s = format!("{room_event:?}"); assert!( !debug_s.contains("secret"), @@ -1030,7 +1029,7 @@ mod tests { #[test] fn sync_timeline_event_serialisation() { - let room_event = SyncTimelineEvent { + let room_event = TimelineEvent { kind: TimelineEventKind::Decrypted(DecryptedRoomEvent { event: Raw::new(&example_event()).unwrap().cast(), encryption_info: EncryptionInfo { @@ -1092,7 +1091,7 @@ mod tests { ); // And it can be properly deserialized from the new format. - let event: SyncTimelineEvent = serde_json::from_value(serialized).unwrap(); + let event: TimelineEvent = serde_json::from_value(serialized).unwrap(); assert_eq!(event.event_id(), Some(event_id!("$xxxxx:example.org").to_owned())); assert_matches!( event.encryption_info().unwrap().algorithm_info, @@ -1121,7 +1120,7 @@ mod tests { "verification_state": "Verified", }, }); - let event: SyncTimelineEvent = serde_json::from_value(serialized).unwrap(); + let event: TimelineEvent = serde_json::from_value(serialized).unwrap(); assert_eq!(event.event_id(), Some(event_id!("$xxxxx:example.org").to_owned())); assert_matches!( event.encryption_info().unwrap().algorithm_info, @@ -1154,7 +1153,7 @@ mod tests { "RelationsReplace": {"UnableToDecrypt": {"session_id": "xyz"}} } }); - let event: SyncTimelineEvent = serde_json::from_value(serialized).unwrap(); + let event: TimelineEvent = serde_json::from_value(serialized).unwrap(); assert_eq!(event.event_id(), Some(event_id!("$xxxxx:example.org").to_owned())); assert_matches!( event.encryption_info().unwrap().algorithm_info, @@ -1220,7 +1219,7 @@ mod tests { assert!(result.is_ok()); // should have migrated to the new format - let event: SyncTimelineEvent = result.unwrap(); + let event: TimelineEvent = result.unwrap(); assert_matches!( event.kind, TimelineEventKind::UnableToDecrypt { utd_info, .. }=> { @@ -1362,7 +1361,7 @@ mod tests { #[test] fn snapshot_test_sync_timeline_event() { - let room_event = SyncTimelineEvent { + let room_event = TimelineEvent { kind: TimelineEventKind::Decrypted(DecryptedRoomEvent { event: Raw::new(&example_event()).unwrap().cast(), encryption_info: EncryptionInfo { diff --git a/crates/matrix-sdk-sqlite/migrations/event_cache_store/003_events.sql b/crates/matrix-sdk-sqlite/migrations/event_cache_store/003_events.sql index 1de6495cc..c3f8e07a0 100644 --- a/crates/matrix-sdk-sqlite/migrations/event_cache_store/003_events.sql +++ b/crates/matrix-sdk-sqlite/migrations/event_cache_store/003_events.sql @@ -36,7 +36,7 @@ CREATE TABLE "events" ( -- `OwnedEventId` for events, can be null if malformed. "event_id" TEXT, - -- JSON serialized `SyncTimelineEvent` (encrypted value). + -- JSON serialized `TimelineEvent` (encrypted value). "content" BLOB NOT NULL, -- Position (index) in the chunk. "position" INTEGER NOT NULL, diff --git a/crates/matrix-sdk-ui/src/notification_client.rs b/crates/matrix-sdk-ui/src/notification_client.rs index b896fa64f..5af760e71 100644 --- a/crates/matrix-sdk-ui/src/notification_client.rs +++ b/crates/matrix-sdk-ui/src/notification_client.rs @@ -20,7 +20,7 @@ use std::{ use futures_util::{pin_mut, StreamExt as _}; use matrix_sdk::{room::Room, Client, ClientBuildError, SlidingSyncList, SlidingSyncMode}; use matrix_sdk_base::{ - deserialized_responses::SyncTimelineEvent, sliding_sync::http, RoomState, StoreError, + deserialized_responses::TimelineEvent, sliding_sync::http, RoomState, StoreError, }; use ruma::{ assign, @@ -159,7 +159,7 @@ impl NotificationClient { &self, room: &Room, raw_event: &Raw, - ) -> Result, Error> { + ) -> Result, Error> { let event: AnySyncTimelineEvent = raw_event.deserialize().map_err(|_| Error::InvalidRumaEvent)?; diff --git a/crates/matrix-sdk-ui/src/timeline/controller/mod.rs b/crates/matrix-sdk-ui/src/timeline/controller/mod.rs index 75c7f8983..e5f963d15 100644 --- a/crates/matrix-sdk-ui/src/timeline/controller/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/controller/mod.rs @@ -22,7 +22,7 @@ use imbl::Vector; #[cfg(test)] use matrix_sdk::{crypto::OlmMachine, SendOutsideWasm}; use matrix_sdk::{ - deserialized_responses::{SyncTimelineEvent, TimelineEventKind as SdkTimelineEventKind}, + deserialized_responses::{TimelineEvent, TimelineEventKind as SdkTimelineEventKind}, event_cache::{paginator::Paginator, RoomEventCache}, send_queue::{ LocalEcho, LocalEchoContent, RoomSendQueueUpdate, SendHandle, SendReactionHandle, @@ -396,7 +396,7 @@ impl TimelineController

{ pub(crate) async fn reload_pinned_events( &self, - ) -> Result, PinnedEventsLoaderError> { + ) -> Result, PinnedEventsLoaderError> { let focus_guard = self.focus.read().await; if let TimelineFocusData::PinnedEvents { loader } = &*focus_guard { @@ -663,7 +663,7 @@ impl TimelineController

{ ) -> HandleManyEventsResult where Events: IntoIterator + ExactSizeIterator, - ::Item: Into, + ::Item: Into, { if events.len() == 0 { return Default::default(); @@ -676,7 +676,7 @@ impl TimelineController

{ /// Handle updates on events as [`VectorDiff`]s. pub(super) async fn handle_remote_events_with_diffs( &self, - diffs: Vec>, + diffs: Vec>, origin: RemoteEventOrigin, ) { if diffs.is_empty() { @@ -711,7 +711,7 @@ impl TimelineController

{ origin: RemoteEventOrigin, ) where Events: IntoIterator + ExactSizeIterator, - ::Item: Into, + ::Item: Into, { let mut state = self.state.write().await; diff --git a/crates/matrix-sdk-ui/src/timeline/controller/state.rs b/crates/matrix-sdk-ui/src/timeline/controller/state.rs index 5b5d1e59d..18fa85cbe 100644 --- a/crates/matrix-sdk-ui/src/timeline/controller/state.rs +++ b/crates/matrix-sdk-ui/src/timeline/controller/state.rs @@ -22,7 +22,7 @@ use std::{ use eyeball_im::VectorDiff; use itertools::Itertools as _; use matrix_sdk::{ - deserialized_responses::SyncTimelineEvent, ring_buffer::RingBuffer, send_queue::SendHandle, + deserialized_responses::TimelineEvent, ring_buffer::RingBuffer, send_queue::SendHandle, }; #[cfg(test)] use ruma::events::receipt::ReceiptEventContent; @@ -138,7 +138,7 @@ impl TimelineState { ) -> HandleManyEventsResult where Events: IntoIterator + ExactSizeIterator, - ::Item: Into, + ::Item: Into, RoomData: RoomDataProvider, { if events.len() == 0 { @@ -156,7 +156,7 @@ impl TimelineState { /// Handle updates on events as [`VectorDiff`]s. pub(super) async fn handle_remote_events_with_diffs( &mut self, - diffs: Vec>, + diffs: Vec>, origin: RemoteEventOrigin, room_data: &RoomData, settings: &TimelineSettings, @@ -256,7 +256,7 @@ impl TimelineState { room_data_provider: &P, settings: &TimelineSettings, ) where - Fut: Future>, + Fut: Future>, { let mut txn = self.transaction(); @@ -330,7 +330,7 @@ impl TimelineState { ) -> HandleManyEventsResult where Events: IntoIterator, - Events::Item: Into, + Events::Item: Into, RoomData: RoomDataProvider, { let mut txn = self.transaction(); @@ -396,7 +396,7 @@ impl TimelineStateTransaction<'_> { ) -> HandleManyEventsResult where Events: IntoIterator, - Events::Item: Into, + Events::Item: Into, RoomData: RoomDataProvider, { let mut total = HandleManyEventsResult::default(); @@ -438,7 +438,7 @@ impl TimelineStateTransaction<'_> { /// Handle updates on events as [`VectorDiff`]s. pub(super) async fn handle_remote_events_with_diffs( &mut self, - diffs: Vec>, + diffs: Vec>, origin: RemoteEventOrigin, room_data_provider: &RoomData, settings: &TimelineSettings, @@ -558,13 +558,13 @@ impl TimelineStateTransaction<'_> { /// Returns the number of timeline updates that were made. async fn handle_remote_event( &mut self, - event: SyncTimelineEvent, + event: TimelineEvent, position: TimelineItemPosition, room_data_provider: &P, settings: &TimelineSettings, date_divider_adjuster: &mut DateDividerAdjuster, ) -> HandleEventResult { - let SyncTimelineEvent { push_actions, kind } = event; + let TimelineEvent { push_actions, kind } = event; let encryption_info = kind.encryption_info().cloned(); let (raw, utd_info) = match kind { diff --git a/crates/matrix-sdk-ui/src/timeline/event_handler.rs b/crates/matrix-sdk-ui/src/timeline/event_handler.rs index 5a3d362ea..6759a2e52 100644 --- a/crates/matrix-sdk-ui/src/timeline/event_handler.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_handler.rs @@ -211,7 +211,7 @@ impl TimelineEventKind { Self::UnableToDecrypt { content, utd_cause } } else { // If we get here, it means that some part of the code has created a - // `SyncTimelineEvent` containing an `m.room.encrypted` event + // `TimelineEvent` containing an `m.room.encrypted` event // without decrypting it. Possibly this means that encryption has not been // configured. // We treat it the same as any other message-like event. diff --git a/crates/matrix-sdk-ui/src/timeline/event_item/content/message.rs b/crates/matrix-sdk-ui/src/timeline/event_item/content/message.rs index 06c715eaf..09ee071f9 100644 --- a/crates/matrix-sdk-ui/src/timeline/event_item/content/message.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_item/content/message.rs @@ -17,7 +17,7 @@ use std::{fmt, sync::Arc}; use imbl::{vector, Vector}; -use matrix_sdk::{deserialized_responses::SyncTimelineEvent, Room}; +use matrix_sdk::{deserialized_responses::TimelineEvent, Room}; use ruma::{ assign, events::{ @@ -340,14 +340,14 @@ impl RepliedToEvent { /// Try to create a `RepliedToEvent` from a `TimelineEvent` by providing the /// room. pub async fn try_from_timeline_event_for_room( - timeline_event: SyncTimelineEvent, + timeline_event: TimelineEvent, room_data_provider: &Room, ) -> Result { Self::try_from_timeline_event(timeline_event, room_data_provider).await } pub(in crate::timeline) async fn try_from_timeline_event( - timeline_event: SyncTimelineEvent, + timeline_event: TimelineEvent, room_data_provider: &P, ) -> Result { let event = match timeline_event.raw().deserialize() { diff --git a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs index 9873c0a09..a31ac8dbb 100644 --- a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs @@ -127,14 +127,17 @@ impl EventTimelineItem { Self { sender, sender_profile, timestamp, content, reactions, kind, is_room_encrypted } } - /// If the supplied low-level `SyncTimelineEvent` is suitable for use as the - /// `latest_event` in a message preview, wrap it as an `EventTimelineItem`. + /// If the supplied low-level [`TimelineEvent`] is suitable for use as the + /// `latest_event` in a message preview, wrap it as an + /// `EventTimelineItem`. /// /// **Note:** Timeline items created via this constructor do **not** produce /// the correct ShieldState when calling /// [`get_shield`][EventTimelineItem::get_shield]. This is because they are /// intended for display in the room list which a) is unlikely to show /// shields and b) would incur a significant performance overhead. + /// + /// [`TimelineEvent`]: matrix_sdk::deserialized_responses::TimelineEvent pub async fn from_latest_event( client: Client, room_id: &RoomId, @@ -754,7 +757,7 @@ mod tests { use assert_matches2::assert_let; use matrix_sdk::test_utils::logged_in_client; use matrix_sdk_base::{ - deserialized_responses::SyncTimelineEvent, latest_event::LatestEvent, sliding_sync::http, + deserialized_responses::TimelineEvent, latest_event::LatestEvent, sliding_sync::http, MinimalStateEvent, OriginalMinimalStateEvent, }; use matrix_sdk_test::{ @@ -844,7 +847,7 @@ mod tests { client.process_sliding_sync_test_helper(&response).await.unwrap(); // When we construct a timeline event from it - let event = SyncTimelineEvent::new(raw_event.cast()); + let event = TimelineEvent::new(raw_event.cast()); let timeline_item = EventTimelineItem::from_latest_event(client, room_id, LatestEvent::new(event)) .await @@ -1121,8 +1124,8 @@ mod tests { body: &str, formatted_body: &str, ts: u64, - ) -> SyncTimelineEvent { - SyncTimelineEvent::new(sync_timeline_event!({ + ) -> TimelineEvent { + TimelineEvent::new(sync_timeline_event!({ "event_id": "$eventid6", "sender": user_id, "origin_server_ts": ts, diff --git a/crates/matrix-sdk-ui/src/timeline/pinned_events_loader.rs b/crates/matrix-sdk-ui/src/timeline/pinned_events_loader.rs index 2fa07fd67..53b844411 100644 --- a/crates/matrix-sdk-ui/src/timeline/pinned_events_loader.rs +++ b/crates/matrix-sdk-ui/src/timeline/pinned_events_loader.rs @@ -19,7 +19,7 @@ use matrix_sdk::{ config::RequestConfig, event_cache::paginator::PaginatorError, BoxFuture, Room, SendOutsideWasm, SyncOutsideWasm, }; -use matrix_sdk_base::deserialized_responses::SyncTimelineEvent; +use matrix_sdk_base::deserialized_responses::TimelineEvent; use ruma::{events::relation::RelationType, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId}; use thiserror::Error; use tracing::{debug, warn}; @@ -55,9 +55,9 @@ impl PinnedEventsLoader { /// `max_concurrent_requests` allows, to avoid overwhelming the server. /// /// It returns a `Result` with either a - /// chronologically sorted list of retrieved `SyncTimelineEvent`s - /// or a `PinnedEventsLoaderError`. - pub async fn load_events(&self) -> Result, PinnedEventsLoaderError> { + /// chronologically sorted list of retrieved [`TimelineEvent`]s + /// or a [`PinnedEventsLoaderError`]. + pub async fn load_events(&self) -> Result, PinnedEventsLoaderError> { let pinned_event_ids: Vec = self .room .pinned_event_ids() @@ -74,7 +74,7 @@ impl PinnedEventsLoader { let request_config = Some(RequestConfig::default().retry_limit(3)); - let mut loaded_events: Vec = + let mut loaded_events: Vec = stream::iter(pinned_event_ids.into_iter().map(|event_id| { let provider = self.room.clone(); let relations_filter = @@ -132,7 +132,7 @@ pub trait PinnedEventsRoom: SendOutsideWasm + SyncOutsideWasm { event_id: &'a EventId, request_config: Option, related_event_filters: Option>, - ) -> BoxFuture<'a, Result<(SyncTimelineEvent, Vec), PaginatorError>>; + ) -> BoxFuture<'a, Result<(TimelineEvent, Vec), PaginatorError>>; /// Get the pinned event ids for a room. fn pinned_event_ids(&self) -> Option>; @@ -150,7 +150,7 @@ impl PinnedEventsRoom for Room { event_id: &'a EventId, request_config: Option, related_event_filters: Option>, - ) -> BoxFuture<'a, Result<(SyncTimelineEvent, Vec), PaginatorError>> { + ) -> BoxFuture<'a, Result<(TimelineEvent, Vec), PaginatorError>> { Box::pin(async move { if let Ok((cache, _handles)) = self.event_cache().await { if let Some(ret) = cache.event_with_relations(event_id, related_event_filters).await diff --git a/crates/matrix-sdk-ui/src/timeline/tests/basic.rs b/crates/matrix-sdk-ui/src/timeline/tests/basic.rs index a93502832..43126c023 100644 --- a/crates/matrix-sdk-ui/src/timeline/tests/basic.rs +++ b/crates/matrix-sdk-ui/src/timeline/tests/basic.rs @@ -16,7 +16,7 @@ use assert_matches::assert_matches; use assert_matches2::assert_let; use eyeball_im::VectorDiff; use futures_util::StreamExt; -use matrix_sdk::deserialized_responses::SyncTimelineEvent; +use matrix_sdk::deserialized_responses::TimelineEvent; use matrix_sdk_test::{ async_test, event_factory::PreviousMembership, sync_timeline_event, ALICE, BOB, CAROL, }; @@ -122,7 +122,7 @@ async fn test_sticker() { let mut stream = timeline.subscribe_events().await; timeline - .handle_live_event(SyncTimelineEvent::new(sync_timeline_event!({ + .handle_live_event(TimelineEvent::new(sync_timeline_event!({ "content": { "body": "Happy sticker", "info": { diff --git a/crates/matrix-sdk-ui/src/timeline/tests/edit.rs b/crates/matrix-sdk-ui/src/timeline/tests/edit.rs index ed90d46c5..5c00e2186 100644 --- a/crates/matrix-sdk-ui/src/timeline/tests/edit.rs +++ b/crates/matrix-sdk-ui/src/timeline/tests/edit.rs @@ -19,7 +19,7 @@ use eyeball_im::VectorDiff; use matrix_sdk::deserialized_responses::{ AlgorithmInfo, EncryptionInfo, VerificationLevel, VerificationState, }; -use matrix_sdk_base::deserialized_responses::{DecryptedRoomEvent, SyncTimelineEvent}; +use matrix_sdk_base::deserialized_responses::{DecryptedRoomEvent, TimelineEvent}; use matrix_sdk_test::{async_test, ALICE}; use ruma::{ event_id, @@ -178,7 +178,7 @@ async fn test_edit_updates_encryption_info() { verification_state: VerificationState::Verified, }; - let original_event: SyncTimelineEvent = DecryptedRoomEvent { + let original_event: TimelineEvent = DecryptedRoomEvent { event: original_event.cast(), encryption_info: encryption_info.clone(), unsigned_encryption_info: None, @@ -207,7 +207,7 @@ async fn test_edit_updates_encryption_info() { .into_raw_timeline(); encryption_info.verification_state = VerificationState::Unverified(VerificationLevel::UnverifiedIdentity); - let edit_event: SyncTimelineEvent = DecryptedRoomEvent { + let edit_event: TimelineEvent = DecryptedRoomEvent { event: edit_event.cast(), encryption_info: encryption_info.clone(), unsigned_encryption_info: None, diff --git a/crates/matrix-sdk-ui/src/timeline/tests/encryption.rs b/crates/matrix-sdk-ui/src/timeline/tests/encryption.rs index d50fdb76f..40799138d 100644 --- a/crates/matrix-sdk-ui/src/timeline/tests/encryption.rs +++ b/crates/matrix-sdk-ui/src/timeline/tests/encryption.rs @@ -30,7 +30,7 @@ use matrix_sdk::{ crypto::{decrypt_room_key_export, types::events::UtdCause, OlmMachine}, test_utils::test_client_builder, }; -use matrix_sdk_base::deserialized_responses::{SyncTimelineEvent, UnableToDecryptReason}; +use matrix_sdk_base::deserialized_responses::{TimelineEvent, UnableToDecryptReason}; use matrix_sdk_test::{async_test, ALICE, BOB}; use ruma::{ assign, event_id, @@ -750,7 +750,7 @@ async fn test_retry_decryption_updates_response() { } } -fn utd_event_with_unsigned(unsigned: serde_json::Value) -> SyncTimelineEvent { +fn utd_event_with_unsigned(unsigned: serde_json::Value) -> TimelineEvent { let raw = Raw::from_json( to_raw_value(&json!({ "event_id": "$myevent", @@ -770,7 +770,7 @@ fn utd_event_with_unsigned(unsigned: serde_json::Value) -> SyncTimelineEvent { .unwrap(), ); - SyncTimelineEvent::new_utd_event( + TimelineEvent::new_utd_event( raw, matrix_sdk::deserialized_responses::UnableToDecryptInfo { session_id: Some("SESSION_ID".into()), diff --git a/crates/matrix-sdk-ui/src/timeline/tests/event_filter.rs b/crates/matrix-sdk-ui/src/timeline/tests/event_filter.rs index 5cfd714f6..da1d7d400 100644 --- a/crates/matrix-sdk-ui/src/timeline/tests/event_filter.rs +++ b/crates/matrix-sdk-ui/src/timeline/tests/event_filter.rs @@ -17,7 +17,7 @@ use std::sync::Arc; use assert_matches::assert_matches; use assert_matches2::assert_let; use eyeball_im::VectorDiff; -use matrix_sdk::deserialized_responses::SyncTimelineEvent; +use matrix_sdk::deserialized_responses::TimelineEvent; use matrix_sdk_test::{async_test, sync_timeline_event, ALICE, BOB}; use ruma::events::{ room::{ @@ -141,7 +141,7 @@ async fn test_hide_failed_to_parse() { // m.room.message events must have a msgtype and body in content, so this // event with an empty content object should fail to deserialize. timeline - .handle_live_event(SyncTimelineEvent::new(sync_timeline_event!({ + .handle_live_event(TimelineEvent::new(sync_timeline_event!({ "content": {}, "event_id": "$eeG0HA0FAZ37wP8kXlNkxx3I", "origin_server_ts": 10, @@ -153,7 +153,7 @@ async fn test_hide_failed_to_parse() { // Similar to above, the m.room.member state event must also not have an // empty content object. timeline - .handle_live_event(SyncTimelineEvent::new(sync_timeline_event!({ + .handle_live_event(TimelineEvent::new(sync_timeline_event!({ "content": {}, "event_id": "$d5G0HA0FAZ37wP8kXlNkxx3I", "origin_server_ts": 2179, diff --git a/crates/matrix-sdk-ui/src/timeline/tests/invalid.rs b/crates/matrix-sdk-ui/src/timeline/tests/invalid.rs index 8f3a08830..1340c6a09 100644 --- a/crates/matrix-sdk-ui/src/timeline/tests/invalid.rs +++ b/crates/matrix-sdk-ui/src/timeline/tests/invalid.rs @@ -14,7 +14,7 @@ use assert_matches2::assert_let; use eyeball_im::VectorDiff; -use matrix_sdk::deserialized_responses::SyncTimelineEvent; +use matrix_sdk::deserialized_responses::TimelineEvent; use matrix_sdk_test::{async_test, sync_timeline_event, ALICE, BOB}; use ruma::{ events::{room::message::MessageType, MessageLikeEventType, StateEventType}, @@ -60,7 +60,7 @@ async fn test_invalid_event_content() { // m.room.message events must have a msgtype and body in content, so this // event with an empty content object should fail to deserialize. timeline - .handle_live_event(SyncTimelineEvent::new(sync_timeline_event!({ + .handle_live_event(TimelineEvent::new(sync_timeline_event!({ "content": {}, "event_id": "$eeG0HA0FAZ37wP8kXlNkxx3I", "origin_server_ts": 10, @@ -79,7 +79,7 @@ async fn test_invalid_event_content() { // Similar to above, the m.room.member state event must also not have an // empty content object. timeline - .handle_live_event(SyncTimelineEvent::new(sync_timeline_event!({ + .handle_live_event(TimelineEvent::new(sync_timeline_event!({ "content": {}, "event_id": "$d5G0HA0FAZ37wP8kXlNkxx3I", "origin_server_ts": 2179, @@ -107,7 +107,7 @@ async fn test_invalid_event() { // This event is missing the sender field which the homeserver must add to // all timeline events. Because the event is malformed, it will be ignored. timeline - .handle_live_event(SyncTimelineEvent::new(sync_timeline_event!({ + .handle_live_event(TimelineEvent::new(sync_timeline_event!({ "content": { "body": "hello world", "msgtype": "m.text" diff --git a/crates/matrix-sdk-ui/src/timeline/tests/mod.rs b/crates/matrix-sdk-ui/src/timeline/tests/mod.rs index 61aa0ca9f..2c23569b2 100644 --- a/crates/matrix-sdk-ui/src/timeline/tests/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/tests/mod.rs @@ -27,7 +27,7 @@ use futures_core::Stream; use indexmap::IndexMap; use matrix_sdk::{ config::RequestConfig, - deserialized_responses::SyncTimelineEvent, + deserialized_responses::TimelineEvent, event_cache::paginator::{PaginableRoom, PaginatorError}, room::{EventWithContextResponse, Messages, MessagesOptions}, send_queue::RoomSendQueueUpdate, @@ -173,7 +173,7 @@ impl TestTimeline { self.controller.items().await.len() } - async fn handle_live_event(&self, event: impl Into) { + async fn handle_live_event(&self, event: impl Into) { let event = event.into(); self.controller .add_events_at( @@ -196,7 +196,7 @@ impl TestTimeline { } async fn handle_back_paginated_event(&self, event: Raw) { - let timeline_event = SyncTimelineEvent::new(event.cast()); + let timeline_event = TimelineEvent::new(event.cast()); self.controller .add_events_at( [timeline_event].into_iter(), @@ -297,7 +297,7 @@ impl PinnedEventsRoom for TestRoomDataProvider { _event_id: &'a EventId, _request_config: Option, _related_event_filters: Option>, - ) -> BoxFuture<'a, Result<(SyncTimelineEvent, Vec), PaginatorError>> { + ) -> BoxFuture<'a, Result<(TimelineEvent, Vec), PaginatorError>> { unimplemented!(); } diff --git a/crates/matrix-sdk-ui/src/timeline/tests/reactions.rs b/crates/matrix-sdk-ui/src/timeline/tests/reactions.rs index cea3ffc18..9f42e1013 100644 --- a/crates/matrix-sdk-ui/src/timeline/tests/reactions.rs +++ b/crates/matrix-sdk-ui/src/timeline/tests/reactions.rs @@ -18,7 +18,7 @@ use assert_matches2::{assert_let, assert_matches}; use eyeball_im::VectorDiff; use futures_core::Stream; use futures_util::{FutureExt as _, StreamExt as _}; -use matrix_sdk::deserialized_responses::SyncTimelineEvent; +use matrix_sdk::deserialized_responses::TimelineEvent; use matrix_sdk_test::{async_test, event_factory::EventFactory, sync_timeline_event, ALICE, BOB}; use ruma::{ event_id, events::AnyMessageLikeEventContent, server_name, uint, EventId, @@ -151,7 +151,7 @@ async fn test_redact_reaction_success() { // When that redaction is confirmed by the server, timeline - .handle_live_event(SyncTimelineEvent::new(sync_timeline_event!({ + .handle_live_event(TimelineEvent::new(sync_timeline_event!({ "sender": *ALICE, "type": "m.room.redaction", "event_id": "$idb", diff --git a/crates/matrix-sdk-ui/src/timeline/tests/shields.rs b/crates/matrix-sdk-ui/src/timeline/tests/shields.rs index c7f50c267..4dd6cb985 100644 --- a/crates/matrix-sdk-ui/src/timeline/tests/shields.rs +++ b/crates/matrix-sdk-ui/src/timeline/tests/shields.rs @@ -1,6 +1,6 @@ use assert_matches::assert_matches; use eyeball_im::VectorDiff; -use matrix_sdk_base::deserialized_responses::{ShieldState, ShieldStateCode, SyncTimelineEvent}; +use matrix_sdk_base::deserialized_responses::{ShieldState, ShieldStateCode, TimelineEvent}; use matrix_sdk_test::{async_test, sync_timeline_event, ALICE}; use ruma::{ event_id, @@ -97,7 +97,7 @@ async fn test_local_sent_in_clear_shield() { // When the remote echo comes in. timeline - .handle_live_event(SyncTimelineEvent::new(sync_timeline_event!({ + .handle_live_event(TimelineEvent::new(sync_timeline_event!({ "content": { "body": "Local message", "msgtype": "m.text", diff --git a/crates/matrix-sdk-ui/src/timeline/traits.rs b/crates/matrix-sdk-ui/src/timeline/traits.rs index 912b1fd0b..55cae54db 100644 --- a/crates/matrix-sdk-ui/src/timeline/traits.rs +++ b/crates/matrix-sdk-ui/src/timeline/traits.rs @@ -19,7 +19,7 @@ use indexmap::IndexMap; #[cfg(test)] use matrix_sdk::crypto::{DecryptionSettings, RoomEventDecryptionResult, TrustRequirement}; use matrix_sdk::{ - crypto::types::events::CryptoContextInfo, deserialized_responses::SyncTimelineEvent, + crypto::types::events::CryptoContextInfo, deserialized_responses::TimelineEvent, event_cache::paginator::PaginableRoom, AsyncTraitDeps, Result, Room, SendOutsideWasm, }; use matrix_sdk_base::{latest_event::LatestEvent, RoomInfo}; @@ -281,24 +281,18 @@ pub(super) trait Decryptor: AsyncTraitDeps + Clone + 'static { fn decrypt_event_impl( &self, raw: &Raw, - ) -> impl Future> + SendOutsideWasm; + ) -> impl Future> + SendOutsideWasm; } impl Decryptor for Room { - async fn decrypt_event_impl( - &self, - raw: &Raw, - ) -> Result { + async fn decrypt_event_impl(&self, raw: &Raw) -> Result { self.decrypt_event(raw.cast_ref()).await } } #[cfg(test)] impl Decryptor for (matrix_sdk_base::crypto::OlmMachine, ruma::OwnedRoomId) { - async fn decrypt_event_impl( - &self, - raw: &Raw, - ) -> Result { + async fn decrypt_event_impl(&self, raw: &Raw) -> Result { let (olm_machine, room_id) = self; let decryption_settings = DecryptionSettings { sender_device_trust_requirement: TrustRequirement::Untrusted }; @@ -308,7 +302,7 @@ impl Decryptor for (matrix_sdk_base::crypto::OlmMachine, ruma::OwnedRoomId) { { RoomEventDecryptionResult::Decrypted(decrypted) => Ok(decrypted.into()), RoomEventDecryptionResult::UnableToDecrypt(utd_info) => { - Ok(SyncTimelineEvent::new_utd_event(raw.clone(), utd_info)) + Ok(TimelineEvent::new_utd_event(raw.clone(), utd_info)) } } } diff --git a/crates/matrix-sdk-ui/tests/integration/main.rs b/crates/matrix-sdk-ui/tests/integration/main.rs index e59f31128..2c575ca81 100644 --- a/crates/matrix-sdk-ui/tests/integration/main.rs +++ b/crates/matrix-sdk-ui/tests/integration/main.rs @@ -13,7 +13,7 @@ // limitations under the License. use itertools::Itertools as _; -use matrix_sdk::deserialized_responses::SyncTimelineEvent; +use matrix_sdk::deserialized_responses::TimelineEvent; use ruma::{events::AnyStateEvent, serde::Raw, EventId, RoomId}; use serde::Serialize; use serde_json::json; @@ -62,9 +62,9 @@ async fn mock_context( room_id: &RoomId, event_id: &EventId, prev_batch_token: Option, - events_before: Vec, - event: SyncTimelineEvent, - events_after: Vec, + events_before: Vec, + event: TimelineEvent, + events_after: Vec, next_batch_token: Option, state: Vec>, ) { @@ -92,7 +92,7 @@ async fn mock_messages( server: &MockServer, start: String, end: Option, - chunk: Vec, + chunk: Vec, state: Vec>, ) { Mock::given(method("GET")) diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/pinned_event.rs b/crates/matrix-sdk-ui/tests/integration/timeline/pinned_event.rs index d7c57621e..943d6d5f5 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/pinned_event.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/pinned_event.rs @@ -13,7 +13,7 @@ use matrix_sdk::{ }, Client, Room, }; -use matrix_sdk_base::deserialized_responses::SyncTimelineEvent; +use matrix_sdk_base::deserialized_responses::TimelineEvent; use matrix_sdk_test::{ async_test, event_factory::EventFactory, JoinedRoomBuilder, StateTestEvent, SyncResponseBuilder, BOB, @@ -880,7 +880,7 @@ async fn mock_events_endpoint( .mock_room_event() .room(room_id.to_owned()) .match_event_id() - .ok(SyncTimelineEvent::new(event.cast())) + .ok(TimelineEvent::new(event.cast())) .mount() .await; } diff --git a/crates/matrix-sdk/src/event_cache/deduplicator.rs b/crates/matrix-sdk/src/event_cache/deduplicator.rs index 7dbd54eec..9714ab516 100644 --- a/crates/matrix-sdk/src/event_cache/deduplicator.rs +++ b/crates/matrix-sdk/src/event_cache/deduplicator.rs @@ -155,13 +155,13 @@ pub enum Decoration { #[cfg(test)] mod tests { use assert_matches2::{assert_let, assert_matches}; - use matrix_sdk_base::deserialized_responses::SyncTimelineEvent; + use matrix_sdk_base::deserialized_responses::TimelineEvent; use matrix_sdk_test::event_factory::EventFactory; use ruma::{owned_event_id, user_id, EventId}; use super::*; - fn sync_timeline_event(event_id: &EventId) -> SyncTimelineEvent { + fn sync_timeline_event(event_id: &EventId) -> TimelineEvent { EventFactory::new() .text_msg("") .sender(user_id!("@mnt_io:matrix.org")) diff --git a/crates/matrix-sdk/src/event_cache/mod.rs b/crates/matrix-sdk/src/event_cache/mod.rs index ddc29a85b..4c4622229 100644 --- a/crates/matrix-sdk/src/event_cache/mod.rs +++ b/crates/matrix-sdk/src/event_cache/mod.rs @@ -36,7 +36,7 @@ use std::{ use eyeball::Subscriber; use eyeball_im::VectorDiff; use matrix_sdk_base::{ - deserialized_responses::{AmbiguityChange, SyncTimelineEvent}, + deserialized_responses::{AmbiguityChange, TimelineEvent}, event_cache::store::{EventCacheStoreError, EventCacheStoreLock}, store_locks::LockStoreError, sync::RoomUpdates, @@ -214,7 +214,7 @@ impl EventCache { /// Try to find an event by its ID in all the rooms. // Note: replace this with a select-by-id query when this is implemented in a // store. - pub async fn event(&self, event_id: &EventId) -> Option { + pub async fn event(&self, event_id: &EventId) -> Option { self.inner .all_events .read() @@ -323,7 +323,7 @@ impl EventCache { pub async fn add_initial_events( &self, room_id: &RoomId, - events: Vec, + events: Vec, prev_batch: Option, ) -> Result<()> { // If the event cache's storage has been enabled, do nothing. @@ -352,7 +352,7 @@ impl EventCache { } } -type AllEventsMap = BTreeMap; +type AllEventsMap = BTreeMap; type RelationsMap = BTreeMap>; /// Cache wrapper containing both copies of received events and lists of event @@ -374,7 +374,7 @@ impl AllEventsCache { /// If the event is related to another one, its id is added to the relations /// map. - fn append_related_event(&mut self, event: &SyncTimelineEvent) { + fn append_related_event(&mut self, event: &TimelineEvent) { // Handle and cache events and relations. let Ok(AnySyncTimelineEvent::MessageLike(ev)) = event.raw().deserialize() else { return; @@ -452,7 +452,7 @@ impl AllEventsCache { &self, event_id: &EventId, filter: Option<&[RelationType]>, - ) -> Vec { + ) -> Vec { let mut results = Vec::new(); self.collect_related_events_rec(event_id, filter, &mut results); results @@ -462,7 +462,7 @@ impl AllEventsCache { &self, event_id: &EventId, filter: Option<&[RelationType]>, - results: &mut Vec, + results: &mut Vec, ) { let Some(related_event_ids) = self.relations.get(event_id) else { return; @@ -662,7 +662,7 @@ pub struct BackPaginationOutcome { /// technically, the last one in the topological ordering). /// /// Note: they're not deduplicated (TODO: smart reconciliation). - pub events: Vec, + pub events: Vec, } /// An update related to events happened in a room. @@ -689,7 +689,7 @@ pub enum RoomEventCacheUpdate { /// The room has received updates for the timeline as _diffs_. UpdateTimelineEvents { /// Diffs to apply to the timeline. - diffs: Vec>, + diffs: Vec>, /// Where the diffs are coming from. origin: EventsOrigin, diff --git a/crates/matrix-sdk/src/event_cache/pagination.rs b/crates/matrix-sdk/src/event_cache/pagination.rs index d3ef7fab3..a0ae902a5 100644 --- a/crates/matrix-sdk/src/event_cache/pagination.rs +++ b/crates/matrix-sdk/src/event_cache/pagination.rs @@ -17,7 +17,7 @@ use std::{future::Future, ops::ControlFlow, sync::Arc, time::Duration}; use eyeball::Subscriber; -use matrix_sdk_base::deserialized_responses::SyncTimelineEvent; +use matrix_sdk_base::deserialized_responses::TimelineEvent; use matrix_sdk_common::linked_chunk::ChunkContent; use tokio::time::timeout; use tracing::{debug, instrument, trace}; @@ -181,7 +181,7 @@ impl RoomPagination { // (backward). The `RoomEvents` API expects the first event to be the oldest. .rev() .cloned() - .map(SyncTimelineEvent::from) + .map(TimelineEvent::from) .collect::>(); let first_event_pos = room_events.events().next().map(|(item_pos, _)| item_pos); diff --git a/crates/matrix-sdk/src/event_cache/paginator.rs b/crates/matrix-sdk/src/event_cache/paginator.rs index e3bd46301..fa445f3cf 100644 --- a/crates/matrix-sdk/src/event_cache/paginator.rs +++ b/crates/matrix-sdk/src/event_cache/paginator.rs @@ -20,9 +20,7 @@ use std::{future::Future, sync::Mutex}; use eyeball::{SharedObservable, Subscriber}; -use matrix_sdk_base::{ - deserialized_responses::SyncTimelineEvent, SendOutsideWasm, SyncOutsideWasm, -}; +use matrix_sdk_base::{deserialized_responses::TimelineEvent, SendOutsideWasm, SyncOutsideWasm}; use ruma::{api::Direction, EventId, OwnedEventId, UInt}; use super::pagination::PaginationToken; @@ -118,7 +116,7 @@ pub struct PaginationResult { /// /// If this is the result of a forward pagination, then the events are in /// topological order. - pub events: Vec, + pub events: Vec, /// Did we hit *an* end of the timeline? /// @@ -134,7 +132,7 @@ pub struct PaginationResult { #[derive(Debug)] pub struct StartFromResult { /// All the events returned during this pagination, in topological ordering. - pub events: Vec, + pub events: Vec, /// Whether the /context query returned a previous batch token. pub has_prev: bool, @@ -538,7 +536,7 @@ mod tests { use assert_matches2::assert_let; use futures_core::Future; use futures_util::FutureExt as _; - use matrix_sdk_base::deserialized_responses::SyncTimelineEvent; + use matrix_sdk_base::deserialized_responses::TimelineEvent; use matrix_sdk_test::{async_test, event_factory::EventFactory}; use once_cell::sync::Lazy; use ruma::{api::Direction, event_id, room_id, uint, user_id, EventId, RoomId, UInt, UserId}; @@ -561,8 +559,8 @@ mod tests { wait_for_ready: bool, target_event_text: Arc>, - next_events: Arc>>, - prev_events: Arc>>, + next_events: Arc>>, + prev_events: Arc>>, prev_batch_token: Arc>>, next_batch_token: Arc>>, diff --git a/crates/matrix-sdk/src/event_cache/room/mod.rs b/crates/matrix-sdk/src/event_cache/room/mod.rs index 93a5f4280..c48bcbdf6 100644 --- a/crates/matrix-sdk/src/event_cache/room/mod.rs +++ b/crates/matrix-sdk/src/event_cache/room/mod.rs @@ -18,7 +18,7 @@ use std::{collections::BTreeMap, fmt, sync::Arc}; use events::Gap; use matrix_sdk_base::{ - deserialized_responses::{AmbiguityChange, SyncTimelineEvent}, + deserialized_responses::{AmbiguityChange, TimelineEvent}, linked_chunk::ChunkContent, sync::{JoinedRoomUpdate, LeftRoomUpdate, Timeline}, }; @@ -77,9 +77,7 @@ impl RoomEventCache { /// Subscribe to this room updates, after getting the initial list of /// events. - pub async fn subscribe( - &self, - ) -> Result<(Vec, Receiver)> { + pub async fn subscribe(&self) -> Result<(Vec, Receiver)> { let state = self.inner.state.read().await; let events = state.events().events().map(|(_position, item)| item.clone()).collect(); @@ -93,7 +91,7 @@ impl RoomEventCache { } /// Try to find an event by id in this room. - pub async fn event(&self, event_id: &EventId) -> Option { + pub async fn event(&self, event_id: &EventId) -> Option { if let Some((room_id, event)) = self.inner.all_events.read().await.events.get(event_id).cloned() { @@ -119,7 +117,7 @@ impl RoomEventCache { &self, event_id: &EventId, filter: Option>, - ) -> Option<(SyncTimelineEvent, Vec)> { + ) -> Option<(TimelineEvent, Vec)> { let cache = self.inner.all_events.read().await; if let Some((_, event)) = cache.events.get(event_id) { let related_events = cache.collect_related_events(event_id, filter.as_deref()); @@ -155,7 +153,7 @@ impl RoomEventCache { // TODO: This doesn't insert the event into the linked chunk. In the future // there'll be no distinction between the linked chunk and the separate // cache. There is a discussion in https://github.com/matrix-org/matrix-rust-sdk/issues/3886. - pub(crate) async fn save_event(&self, event: SyncTimelineEvent) { + pub(crate) async fn save_event(&self, event: TimelineEvent) { if let Some(event_id) = event.event_id() { let mut cache = self.inner.all_events.write().await; @@ -172,7 +170,7 @@ impl RoomEventCache { // TODO: This doesn't insert the event into the linked chunk. In the future // there'll be no distinction between the linked chunk and the separate // cache. There is a discussion in https://github.com/matrix-org/matrix-rust-sdk/issues/3886. - pub(crate) async fn save_events(&self, events: impl IntoIterator) { + pub(crate) async fn save_events(&self, events: impl IntoIterator) { let mut cache = self.inner.all_events.write().await; for event in events { if let Some(event_id) = event.event_id() { @@ -370,7 +368,7 @@ impl RoomEventCacheInner { /// storage, notifying observers. pub(super) async fn replace_all_events_by( &self, - sync_timeline_events: Vec, + sync_timeline_events: Vec, prev_batch: Option, ephemeral_events: Vec>, ambiguity_changes: BTreeMap, @@ -407,7 +405,7 @@ impl RoomEventCacheInner { async fn append_events_locked( &self, state: &mut RoomEventCacheState, - sync_timeline_events: Vec, + sync_timeline_events: Vec, prev_batch: Option, ephemeral_events: Vec>, ambiguity_changes: BTreeMap, @@ -506,7 +504,7 @@ impl RoomEventCacheInner { } /// Create a debug string for a [`ChunkContent`] for an event/gap pair. -fn chunk_debug_string(content: &ChunkContent) -> String { +fn chunk_debug_string(content: &ChunkContent) -> String { match content { ChunkContent::Gap(Gap { prev_token }) => { format!("gap['{prev_token}']") @@ -534,7 +532,7 @@ mod private { use eyeball_im::VectorDiff; use matrix_sdk_base::{ - deserialized_responses::{SyncTimelineEvent, TimelineEventKind}, + deserialized_responses::{TimelineEvent, TimelineEventKind}, event_cache::{ store::{ EventCacheStoreError, EventCacheStoreLock, EventCacheStoreLockGuard, @@ -650,7 +648,7 @@ mod private { let _ = closure(); } - fn strip_relations_from_event(ev: &mut SyncTimelineEvent) { + fn strip_relations_from_event(ev: &mut TimelineEvent) { match &mut ev.kind { TimelineEventKind::Decrypted(decrypted) => { // Remove all information about encryption info for @@ -669,7 +667,7 @@ mod private { } /// Strips the bundled relations from a collection of events. - fn strip_relations_from_events(items: &mut [SyncTimelineEvent]) { + fn strip_relations_from_events(items: &mut [TimelineEvent]) { for ev in items.iter_mut() { Self::strip_relations_from_event(ev); } @@ -755,7 +753,7 @@ mod private { pub async fn with_events_mut O>( &mut self, func: F, - ) -> Result<(O, Vec>), EventCacheError> { + ) -> Result<(O, Vec>), EventCacheError> { let output = func(&mut self.events); self.propagate_changes().await?; let updates_as_vector_diffs = self.events.updates_as_vector_diffs(); @@ -847,7 +845,7 @@ mod tests { store::StoreConfig, sync::{JoinedRoomUpdate, Timeline}, }; - use matrix_sdk_common::deserialized_responses::SyncTimelineEvent; + use matrix_sdk_common::deserialized_responses::TimelineEvent; use matrix_sdk_test::{async_test, event_factory::EventFactory, ALICE, BOB}; use ruma::{ event_id, @@ -1617,8 +1615,8 @@ mod tests { async fn assert_relations( room_id: &RoomId, - original_event: SyncTimelineEvent, - related_event: SyncTimelineEvent, + original_event: TimelineEvent, + related_event: TimelineEvent, event_factory: EventFactory, ) { let client = logged_in_client(None).await; diff --git a/crates/matrix-sdk/src/event_handler/mod.rs b/crates/matrix-sdk/src/event_handler/mod.rs index 4f802210b..a8b8d2946 100644 --- a/crates/matrix-sdk/src/event_handler/mod.rs +++ b/crates/matrix-sdk/src/event_handler/mod.rs @@ -50,7 +50,7 @@ use eyeball::{SharedObservable, Subscriber}; use futures_core::Stream; use futures_util::stream::{FuturesUnordered, StreamExt}; use matrix_sdk_base::{ - deserialized_responses::{EncryptionInfo, SyncTimelineEvent}, + deserialized_responses::{EncryptionInfo, TimelineEvent}, SendOutsideWasm, SyncOutsideWasm, }; use pin_project_lite::pin_project; @@ -380,7 +380,7 @@ impl Client { pub(crate) async fn handle_sync_timeline_events( &self, room: Option<&Room>, - timeline_events: &[SyncTimelineEvent], + timeline_events: &[TimelineEvent], ) -> serde_json::Result<()> { #[derive(Deserialize)] struct TimelineEventDetails<'a> { diff --git a/crates/matrix-sdk/src/room/edit.rs b/crates/matrix-sdk/src/room/edit.rs index 850e70d38..33fe5dbeb 100644 --- a/crates/matrix-sdk/src/room/edit.rs +++ b/crates/matrix-sdk/src/room/edit.rs @@ -16,7 +16,7 @@ use std::future::Future; -use matrix_sdk_base::{deserialized_responses::SyncTimelineEvent, SendOutsideWasm}; +use matrix_sdk_base::{deserialized_responses::TimelineEvent, SendOutsideWasm}; use ruma::{ events::{ poll::unstable_start::{ @@ -129,11 +129,11 @@ trait EventSource { fn get_event( &self, event_id: &EventId, - ) -> impl Future> + SendOutsideWasm; + ) -> impl Future> + SendOutsideWasm; } impl EventSource for &Room { - async fn get_event(&self, event_id: &EventId) -> Result { + async fn get_event(&self, event_id: &EventId) -> Result { match self.event_cache().await { Ok((event_cache, _drop_handles)) => { if let Some(event) = event_cache.event(event_id).await { @@ -359,7 +359,7 @@ mod tests { use std::collections::BTreeMap; use assert_matches2::{assert_let, assert_matches}; - use matrix_sdk_base::deserialized_responses::SyncTimelineEvent; + use matrix_sdk_base::deserialized_responses::TimelineEvent; use matrix_sdk_test::{async_test, event_factory::EventFactory}; use ruma::{ event_id, @@ -378,11 +378,11 @@ mod tests { #[derive(Default)] struct TestEventCache { - events: BTreeMap, + events: BTreeMap, } impl EventSource for TestEventCache { - async fn get_event(&self, event_id: &EventId) -> Result { + async fn get_event(&self, event_id: &EventId) -> Result { Ok(self.events.get(event_id).unwrap().clone()) } } @@ -397,7 +397,7 @@ mod tests { cache.events.insert( event_id.to_owned(), // TODO: use the EventFactory for state events too. - SyncTimelineEvent::new( + TimelineEvent::new( Raw::::from_json_string( json!({ "content": { diff --git a/crates/matrix-sdk/src/room/messages.rs b/crates/matrix-sdk/src/room/messages.rs index 37d2e6b38..7c4709feb 100644 --- a/crates/matrix-sdk/src/room/messages.rs +++ b/crates/matrix-sdk/src/room/messages.rs @@ -14,7 +14,7 @@ use std::fmt; -use matrix_sdk_common::{debug::DebugStructExt as _, deserialized_responses::SyncTimelineEvent}; +use matrix_sdk_common::{debug::DebugStructExt as _, deserialized_responses::TimelineEvent}; use ruma::{ api::{ client::{filter::RoomEventFilter, message::get_message_events}, @@ -134,7 +134,7 @@ pub struct Messages { pub end: Option, /// A list of room events. - pub chunk: Vec, + pub chunk: Vec, /// A list of state events relevant to showing the `chunk`. pub state: Vec>, @@ -148,19 +148,19 @@ pub struct Messages { #[derive(Debug, Default)] pub struct EventWithContextResponse { /// The event targeted by the /context query. - pub event: Option, + pub event: Option, /// Events before the target event, if a non-zero context size was /// requested. /// /// Like the corresponding Ruma response, these are in reverse chronological /// order. - pub events_before: Vec, + pub events_before: Vec, /// Events after the target event, if a non-zero context size was requested. /// /// Like the corresponding Ruma response, these are in chronological order. - pub events_after: Vec, + pub events_after: Vec, /// Token to paginate backwards, aka "start" token. pub prev_batch_token: Option, diff --git a/crates/matrix-sdk/src/room/mod.rs b/crates/matrix-sdk/src/room/mod.rs index cf307f6c1..df74cffb3 100644 --- a/crates/matrix-sdk/src/room/mod.rs +++ b/crates/matrix-sdk/src/room/mod.rs @@ -48,7 +48,7 @@ use matrix_sdk_base::{ #[cfg(all(feature = "e2e-encryption", not(target_arch = "wasm32")))] use matrix_sdk_common::BoxFuture; use matrix_sdk_common::{ - deserialized_responses::SyncTimelineEvent, + deserialized_responses::TimelineEvent, executor::{spawn, JoinHandle}, timeout::timeout, }; @@ -341,10 +341,10 @@ impl Room { if let Ok(event) = self.decrypt_event(event.cast_ref()).await { event } else { - SyncTimelineEvent::new(event.cast()) + TimelineEvent::new(event.cast()) } } else { - SyncTimelineEvent::new(event.cast()) + TimelineEvent::new(event.cast()) }; response.chunk.push(decrypted_event); } @@ -447,7 +447,7 @@ impl Room { /// /// Doesn't return an error `Result` when decryption failed; only logs from /// the crypto crate will indicate so. - async fn try_decrypt_event(&self, event: Raw) -> Result { + async fn try_decrypt_event(&self, event: Raw) -> Result { #[cfg(feature = "e2e-encryption")] if let Ok(AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomEncrypted( SyncMessageLikeEvent::Original(_), @@ -458,7 +458,7 @@ impl Room { } } - let mut event = SyncTimelineEvent::new(event.cast()); + let mut event = TimelineEvent::new(event.cast()); event.push_actions = self.event_push_actions(event.raw()).await?; Ok(event) @@ -472,7 +472,7 @@ impl Room { &self, event_id: &EventId, request_config: Option, - ) -> Result { + ) -> Result { let request = get_room_event::v3::Request::new(self.room_id().to_owned(), event_id.to_owned()); @@ -525,7 +525,7 @@ impl Room { // Save the loaded events into the event cache, if it's set up. if let Ok((cache, _handles)) = self.event_cache().await { - let mut events_to_save: Vec = Vec::new(); + let mut events_to_save: Vec = Vec::new(); if let Some(event) = &target_event { events_to_save.push(event.clone().into()); } @@ -1278,14 +1278,14 @@ impl Room { pub async fn decrypt_event( &self, event: &Raw, - ) -> Result { + ) -> Result { let machine = self.client.olm_machine().await; let machine = machine.as_ref().ok_or(Error::NoOlmMachine)?; let decryption_settings = DecryptionSettings { sender_device_trust_requirement: self.client.base_client().decryption_trust_requirement, }; - let mut event: SyncTimelineEvent = match machine + let mut event: TimelineEvent = match machine .try_decrypt_room_event(event.cast_ref(), self.inner.room_id(), &decryption_settings) .await? { @@ -1295,7 +1295,7 @@ impl Room { .encryption() .backups() .maybe_download_room_key(self.room_id().to_owned(), event.clone()); - SyncTimelineEvent::new_utd_event(event.clone().cast(), utd_info) + TimelineEvent::new_utd_event(event.clone().cast(), utd_info) } }; diff --git a/crates/matrix-sdk/src/sliding_sync/client.rs b/crates/matrix-sdk/src/sliding_sync/client.rs index 7695ae004..6f9935819 100644 --- a/crates/matrix-sdk/src/sliding_sync/client.rs +++ b/crates/matrix-sdk/src/sliding_sync/client.rs @@ -257,7 +257,7 @@ impl PreviousEventsProvider for SlidingSyncPreviousEventsProvider<'_> { fn for_room( &self, room_id: &ruma::RoomId, - ) -> Vector { + ) -> Vector { self.0.get(room_id).map(|room| room.timeline_queue()).unwrap_or_default() } } diff --git a/crates/matrix-sdk/src/sliding_sync/mod.rs b/crates/matrix-sdk/src/sliding_sync/mod.rs index cfdf45105..2256e5c43 100644 --- a/crates/matrix-sdk/src/sliding_sync/mod.rs +++ b/crates/matrix-sdk/src/sliding_sync/mod.rs @@ -36,7 +36,7 @@ use async_stream::stream; pub use client::{Version, VersionBuilder}; use futures_core::stream::Stream; pub use matrix_sdk_base::sliding_sync::http; -use matrix_sdk_common::{deserialized_responses::SyncTimelineEvent, executor::spawn, timer}; +use matrix_sdk_common::{deserialized_responses::TimelineEvent, executor::spawn, timer}; use ruma::{ api::{client::error::ErrorKind, OutgoingRequest}, assign, OwnedEventId, OwnedRoomId, RoomId, @@ -345,7 +345,7 @@ impl SlidingSync { if let Some(joined_room) = sync_response.rooms.join.remove(&room_id) { joined_room.timeline.events } else { - room_data.timeline.drain(..).map(SyncTimelineEvent::new).collect() + room_data.timeline.drain(..).map(TimelineEvent::new).collect() }; match rooms_map.get_mut(&room_id) { @@ -1103,7 +1103,7 @@ mod tests { use assert_matches::assert_matches; use event_listener::Listener; use futures_util::{future::join_all, pin_mut, StreamExt}; - use matrix_sdk_common::deserialized_responses::SyncTimelineEvent; + use matrix_sdk_common::deserialized_responses::TimelineEvent; use matrix_sdk_test::async_test; use ruma::{ api::client::error::ErrorKind, assign, owned_room_id, room_id, serde::Raw, uint, @@ -2268,8 +2268,8 @@ mod tests { #[async_test] async fn test_limited_flag_computation() { - let make_event = |event_id: &str| -> SyncTimelineEvent { - SyncTimelineEvent::new( + let make_event = |event_id: &str| -> TimelineEvent { + TimelineEvent::new( Raw::from_json_string( json!({ "event_id": event_id, diff --git a/crates/matrix-sdk/src/sliding_sync/room.rs b/crates/matrix-sdk/src/sliding_sync/room.rs index 92e83f581..4615fb1d7 100644 --- a/crates/matrix-sdk/src/sliding_sync/room.rs +++ b/crates/matrix-sdk/src/sliding_sync/room.rs @@ -4,7 +4,7 @@ use std::{ }; use eyeball_im::Vector; -use matrix_sdk_base::{deserialized_responses::SyncTimelineEvent, sliding_sync::http}; +use matrix_sdk_base::{deserialized_responses::TimelineEvent, sliding_sync::http}; use ruma::{OwnedRoomId, RoomId}; use serde::{Deserialize, Serialize}; @@ -40,7 +40,7 @@ impl SlidingSyncRoom { pub fn new( room_id: OwnedRoomId, prev_batch: Option, - timeline: Vec, + timeline: Vec, ) -> Self { Self { inner: Arc::new(SlidingSyncRoomInner { @@ -66,14 +66,14 @@ impl SlidingSyncRoom { /// /// Note: This API only exists temporarily, it *will* be removed in the /// future. - pub fn timeline_queue(&self) -> Vector { + pub fn timeline_queue(&self) -> Vector { self.inner.timeline_queue.read().unwrap().clone() } pub(super) fn update( &mut self, room_data: http::response::Room, - timeline_updates: Vec, + timeline_updates: Vec, ) { let http::response::Room { prev_batch, limited, .. } = room_data; @@ -165,7 +165,7 @@ struct SlidingSyncRoomInner { /// /// When persisting the room, this queue is truncated to keep only the last /// N events. - timeline_queue: RwLock>, + timeline_queue: RwLock>, } /// A “frozen” [`SlidingSyncRoom`], i.e. that can be written into, or read from @@ -176,7 +176,7 @@ pub(super) struct FrozenSlidingSyncRoom { #[serde(skip_serializing_if = "Option::is_none")] pub(super) prev_batch: Option, #[serde(rename = "timeline")] - pub(super) timeline_queue: Vector, + pub(super) timeline_queue: Vector, } /// Number of timeline events to keep when [`SlidingSyncRoom`] is saved in the @@ -214,7 +214,7 @@ impl From<&SlidingSyncRoom> for FrozenSlidingSyncRoom { #[cfg(test)] mod tests { use imbl::vector; - use matrix_sdk_common::deserialized_responses::SyncTimelineEvent; + use matrix_sdk_common::deserialized_responses::TimelineEvent; use matrix_sdk_test::async_test; use ruma::{events::room::message::RoomMessageEventContent, room_id, serde::Raw, RoomId}; use serde_json::json; @@ -237,7 +237,7 @@ mod tests { fn new_room_with_timeline( room_id: &RoomId, inner: http::response::Room, - timeline: Vec, + timeline: Vec, ) -> SlidingSyncRoom { SlidingSyncRoom::new(room_id.to_owned(), inner.prev_batch, timeline) } @@ -314,7 +314,7 @@ mod tests { macro_rules! timeline_event { (from $sender:literal with id $event_id:literal at $ts:literal: $message:literal) => { - SyncTimelineEvent::new( + TimelineEvent::new( Raw::new(&json!({ "content": RoomMessageEventContent::text_plain($message), "type": "m.room.message", @@ -605,7 +605,7 @@ mod tests { let frozen_room = FrozenSlidingSyncRoom { room_id: room_id!("!29fhd83h92h0:example.com").to_owned(), prev_batch: Some("foo".to_owned()), - timeline_queue: vector![SyncTimelineEvent::new( + timeline_queue: vector![TimelineEvent::new( Raw::new(&json!({ "content": RoomMessageEventContent::text_plain("let it gooo!"), "type": "m.room.message", @@ -657,7 +657,7 @@ mod tests { let max = NUMBER_OF_TIMELINE_EVENTS_TO_KEEP_FOR_THE_CACHE - 1; let timeline_events = (0..=max) .map(|nth| { - SyncTimelineEvent::new( + TimelineEvent::new( Raw::new(&json!({ "content": RoomMessageEventContent::text_plain(format!("message {nth}")), "type": "m.room.message", @@ -694,7 +694,7 @@ mod tests { let max = NUMBER_OF_TIMELINE_EVENTS_TO_KEEP_FOR_THE_CACHE + 2; let timeline_events = (0..=max) .map(|nth| { - SyncTimelineEvent::new( + TimelineEvent::new( Raw::new(&json!({ "content": RoomMessageEventContent::text_plain(format!("message {nth}")), "type": "m.room.message", diff --git a/crates/matrix-sdk/src/test_utils/mocks.rs b/crates/matrix-sdk/src/test_utils/mocks.rs index 44dda8cf1..34d232b54 100644 --- a/crates/matrix-sdk/src/test_utils/mocks.rs +++ b/crates/matrix-sdk/src/test_utils/mocks.rs @@ -22,7 +22,7 @@ use std::{ sync::{Arc, Mutex}, }; -use matrix_sdk_base::deserialized_responses::SyncTimelineEvent; +use matrix_sdk_base::deserialized_responses::TimelineEvent; use matrix_sdk_test::{ test_json, InvitedRoomBuilder, JoinedRoomBuilder, KnockedRoomBuilder, LeftRoomBuilder, SyncResponseBuilder, @@ -1856,7 +1856,7 @@ impl<'a> MockEndpoint<'a, RoomEventEndpoint> { /// Returns a redact endpoint that emulates success, i.e. the redaction /// event has been sent with the given event id. - pub fn ok(self, event: SyncTimelineEvent) -> MatrixMock<'a> { + pub fn ok(self, event: TimelineEvent) -> MatrixMock<'a> { let event_path = if self.endpoint.match_event_id { let event_id = event.kind.event_id().expect("an event id is required"); // The event id should begin with `$`, which would be taken as the end of the diff --git a/crates/matrix-sdk/src/test_utils/mod.rs b/crates/matrix-sdk/src/test_utils/mod.rs index 6c79fc4c7..0594bb134 100644 --- a/crates/matrix-sdk/src/test_utils/mod.rs +++ b/crates/matrix-sdk/src/test_utils/mod.rs @@ -3,7 +3,7 @@ #![allow(dead_code)] use assert_matches2::assert_let; -use matrix_sdk_base::{deserialized_responses::SyncTimelineEvent, SessionMeta}; +use matrix_sdk_base::{deserialized_responses::TimelineEvent, SessionMeta}; use ruma::{ api::MatrixVersion, device_id, @@ -24,8 +24,8 @@ use crate::{ /// Checks that an event is a message-like text event with the given text. #[track_caller] -pub fn assert_event_matches_msg>(event: &E, expected: &str) { - let event: SyncTimelineEvent = event.clone().into(); +pub fn assert_event_matches_msg>(event: &E, expected: &str) { + let event: TimelineEvent = event.clone().into(); let event = event.raw().deserialize().unwrap(); assert_let!( AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(message)) = event diff --git a/crates/matrix-sdk/tests/integration/event_cache.rs b/crates/matrix-sdk/tests/integration/event_cache.rs index a2e2aaf7d..700c61da7 100644 --- a/crates/matrix-sdk/tests/integration/event_cache.rs +++ b/crates/matrix-sdk/tests/integration/event_cache.rs @@ -9,7 +9,7 @@ use assert_matches2::assert_let; use eyeball_im::VectorDiff; use matrix_sdk::{ assert_let_timeout, assert_next_matches_with_timeout, - deserialized_responses::SyncTimelineEvent, + deserialized_responses::TimelineEvent, event_cache::{ paginator::PaginatorState, BackPaginationOutcome, EventCacheError, PaginationToken, RoomEventCacheUpdate, TimelineHasBeenResetWhilePaginating, @@ -204,7 +204,7 @@ async fn test_ignored_unignored() { /// Small helper for backpagination tests, to wait for things to stabilize. async fn wait_for_initial_events( - events: Vec, + events: Vec, room_stream: &mut broadcast::Receiver, ) { if events.is_empty() { diff --git a/testing/matrix-sdk-test/src/event_factory.rs b/testing/matrix-sdk-test/src/event_factory.rs index 444e315fe..9b6f3e59a 100644 --- a/testing/matrix-sdk-test/src/event_factory.rs +++ b/testing/matrix-sdk-test/src/event_factory.rs @@ -21,7 +21,7 @@ use std::{ use as_variant::as_variant; use matrix_sdk_common::deserialized_responses::{ - SyncTimelineEvent, UnableToDecryptInfo, UnableToDecryptReason, + TimelineEvent, UnableToDecryptInfo, UnableToDecryptReason, }; use ruma::{ events::{ @@ -254,19 +254,19 @@ where Raw::new(&self.construct_json(false)).unwrap().cast() } - pub fn into_sync(self) -> SyncTimelineEvent { - SyncTimelineEvent::new(self.into_raw_sync()) + pub fn into_sync(self) -> TimelineEvent { + TimelineEvent::new(self.into_raw_sync()) } } impl EventBuilder { - /// Turn this event into a SyncTimelineEvent representing a decryption + /// Turn this event into a [`TimelineEvent`] representing a decryption /// failure - pub fn into_utd_sync_timeline_event(self) -> SyncTimelineEvent { + pub fn into_utd_sync_timeline_event(self) -> TimelineEvent { let session_id = as_variant!(&self.content.scheme, EncryptedEventScheme::MegolmV1AesSha2) .map(|content| content.session_id.clone()); - SyncTimelineEvent::new_utd_event( + TimelineEvent::new_utd_event( self.into(), UnableToDecryptInfo { session_id, @@ -354,7 +354,7 @@ where } } -impl From> for SyncTimelineEvent +impl From> for TimelineEvent where E::EventType: Serialize, { From da4b8004f20f7171766f3185f1abde5c3acf90c6 Mon Sep 17 00:00:00 2001 From: Benjamin Bouvier Date: Tue, 21 Jan 2025 15:48:45 +0100 Subject: [PATCH 11/55] docs: add changelog entries --- crates/matrix-sdk-base/CHANGELOG.md | 4 ++++ crates/matrix-sdk-common/CHANGELOG.md | 5 +++++ crates/matrix-sdk/CHANGELOG.md | 5 +++++ 3 files changed, 14 insertions(+) diff --git a/crates/matrix-sdk-base/CHANGELOG.md b/crates/matrix-sdk-base/CHANGELOG.md index 6716adf5e..bb72c478e 100644 --- a/crates/matrix-sdk-base/CHANGELOG.md +++ b/crates/matrix-sdk-base/CHANGELOG.md @@ -11,6 +11,10 @@ All notable changes to this project will be documented in this file. - Replaced `Room::compute_display_name` with the reintroduced `Room::display_name()`. The new method computes a display name, or return a cached value from the previous successful computation. If you need a sync variant, consider using `Room::cached_display_name()`. +- [**breaking**]: The reexported types `SyncTimelineEvent` and `TimelineEvent` have been fused into a single type + `TimelineEvent`, and its field `push_actions` has been made `Option`al (it is set to `None` when + we couldn't compute the push actions, because we lacked some information). + ([#4568](https://github.com/matrix-org/matrix-rust-sdk/pull/4568)) ### Features diff --git a/crates/matrix-sdk-common/CHANGELOG.md b/crates/matrix-sdk-common/CHANGELOG.md index 7eedc0ab0..0af184d85 100644 --- a/crates/matrix-sdk-common/CHANGELOG.md +++ b/crates/matrix-sdk-common/CHANGELOG.md @@ -6,6 +6,11 @@ All notable changes to this project will be documented in this file. ## [Unreleased] - ReleaseDate +- [**breaking**]: `SyncTimelineEvent` and `TimelineEvent` have been fused into a single type + `TimelineEvent`, and its field `push_actions` has been made `Option`al (it is set to `None` when + we couldn't compute the push actions, because we lacked some information). + ([#4568](https://github.com/matrix-org/matrix-rust-sdk/pull/4568)) + ## [0.9.0] - 2024-12-18 ### Bug Fixes diff --git a/crates/matrix-sdk/CHANGELOG.md b/crates/matrix-sdk/CHANGELOG.md index c5397b70f..6bb5cc7d0 100644 --- a/crates/matrix-sdk/CHANGELOG.md +++ b/crates/matrix-sdk/CHANGELOG.md @@ -28,6 +28,11 @@ All notable changes to this project will be documented in this file. ### Refactor +- [**breaking**]: The reexported types `SyncTimelineEvent` and `TimelineEvent` have been fused into a single type + `TimelineEvent`, and its field `push_actions` has been made `Option`al (it is set to `None` when + we couldn't compute the push actions, because we lacked some information). + ([#4568](https://github.com/matrix-org/matrix-rust-sdk/pull/4568)) + - [**breaking**] Move the optional `RequestConfig` argument of the `Client::send()` method to the `with_request_config()` builder method. You should call `Client::send(request).with_request_config(request_config).await` From 041627ec4a56c5871469be84aa8e5f5074976261 Mon Sep 17 00:00:00 2001 From: Benjamin Bouvier Date: Tue, 21 Jan 2025 15:56:04 +0100 Subject: [PATCH 12/55] test: rename `EventFactory::into_sync` to `into_event` --- crates/matrix-sdk-base/src/read_receipts.rs | 37 ++++++++++--------- .../src/timeline/event_item/mod.rs | 4 +- .../matrix-sdk-ui/src/timeline/tests/basic.rs | 14 +++---- .../matrix-sdk-ui/src/timeline/tests/echo.rs | 2 +- .../src/timeline/tests/reactions.rs | 4 +- .../tests/integration/timeline/focus_event.rs | 24 ++++++------ .../src/event_cache/deduplicator.rs | 2 +- .../matrix-sdk/src/event_cache/pagination.rs | 9 +++-- .../matrix-sdk/src/event_cache/paginator.rs | 18 ++++----- .../matrix-sdk/src/event_cache/room/events.rs | 2 +- crates/matrix-sdk/src/event_cache/room/mod.rs | 20 +++++----- crates/matrix-sdk/src/room/edit.rs | 4 +- .../tests/integration/room/joined.rs | 2 +- .../tests/integration/send_queue.rs | 6 +-- testing/matrix-sdk-test/src/event_factory.rs | 4 +- 15 files changed, 77 insertions(+), 75 deletions(-) diff --git a/crates/matrix-sdk-base/src/read_receipts.rs b/crates/matrix-sdk-base/src/read_receipts.rs index 171fd1ad3..ee4282e9d 100644 --- a/crates/matrix-sdk-base/src/read_receipts.rs +++ b/crates/matrix-sdk-base/src/read_receipts.rs @@ -729,7 +729,7 @@ mod tests { .text_msg("A") .sender(user_id) .event_id(event_id!("$ida")) - .into_sync(); + .into_event(); ev.push_actions = Some(push_actions); ev } @@ -919,8 +919,8 @@ mod tests { let mut previous_events = Vector::new(); let f = EventFactory::new(); - let ev1 = f.text_msg("A").sender(other_user_id).event_id(receipt_event_id).into_sync(); - let ev2 = f.text_msg("A").sender(other_user_id).event_id(event_id!("$2")).into_sync(); + let ev1 = f.text_msg("A").sender(other_user_id).event_id(receipt_event_id).into_event(); + let ev2 = f.text_msg("A").sender(other_user_id).event_id(event_id!("$2")).into_event(); let receipt_event = f .read_receipts() @@ -944,7 +944,8 @@ mod tests { previous_events.push_back(ev1); previous_events.push_back(ev2); - let new_event = f.text_msg("A").sender(other_user_id).event_id(event_id!("$3")).into_sync(); + let new_event = + f.text_msg("A").sender(other_user_id).event_id(event_id!("$3")).into_event(); compute_unread_counts( user_id, room_id, @@ -1134,7 +1135,7 @@ mod tests { let events = make_test_events(uid); // An event with no id. - let ev6 = EventFactory::new().text_msg("yolo").sender(uid).no_event_id().into_sync(); + let ev6 = EventFactory::new().text_msg("yolo").sender(uid).no_event_id().into_event(); let index = ReceiptSelector::create_sync_index(events.iter().chain(&[ev6])); @@ -1201,8 +1202,8 @@ mod tests { fn test_receipt_selector_handle_pending_receipts_noop() { let sender = user_id!("@bob:example.org"); let f = EventFactory::new().sender(sender); - let ev1 = f.text_msg("yo").event_id(event_id!("$1")).into_sync(); - let ev2 = f.text_msg("well?").event_id(event_id!("$2")).into_sync(); + let ev1 = f.text_msg("yo").event_id(event_id!("$1")).into_event(); + let ev2 = f.text_msg("well?").event_id(event_id!("$2")).into_event(); let events: Vector<_> = vec![ev1, ev2].into(); { @@ -1237,8 +1238,8 @@ mod tests { fn test_receipt_selector_handle_pending_receipts_doesnt_match_known_events() { let sender = user_id!("@bob:example.org"); let f = EventFactory::new().sender(sender); - let ev1 = f.text_msg("yo").event_id(event_id!("$1")).into_sync(); - let ev2 = f.text_msg("well?").event_id(event_id!("$2")).into_sync(); + let ev1 = f.text_msg("yo").event_id(event_id!("$1")).into_event(); + let ev2 = f.text_msg("well?").event_id(event_id!("$2")).into_event(); let events: Vector<_> = vec![ev1, ev2].into(); { @@ -1274,8 +1275,8 @@ mod tests { fn test_receipt_selector_handle_pending_receipts_matches_known_events_no_initial() { let sender = user_id!("@bob:example.org"); let f = EventFactory::new().sender(sender); - let ev1 = f.text_msg("yo").event_id(event_id!("$1")).into_sync(); - let ev2 = f.text_msg("well?").event_id(event_id!("$2")).into_sync(); + let ev1 = f.text_msg("yo").event_id(event_id!("$1")).into_event(); + let ev2 = f.text_msg("well?").event_id(event_id!("$2")).into_event(); let events: Vector<_> = vec![ev1, ev2].into(); { @@ -1316,8 +1317,8 @@ mod tests { fn test_receipt_selector_handle_pending_receipts_matches_known_events_with_initial() { let sender = user_id!("@bob:example.org"); let f = EventFactory::new().sender(sender); - let ev1 = f.text_msg("yo").event_id(event_id!("$1")).into_sync(); - let ev2 = f.text_msg("well?").event_id(event_id!("$2")).into_sync(); + let ev1 = f.text_msg("yo").event_id(event_id!("$1")).into_event(); + let ev2 = f.text_msg("well?").event_id(event_id!("$2")).into_event(); let events: Vector<_> = vec![ev1, ev2].into(); { @@ -1495,10 +1496,10 @@ mod tests { f.text_msg("A mulatto, an albino") .sender(&myself) .event_id(event_id!("$6")) - .into_sync(), + .into_event(), ); events.push_back( - f.text_msg("A mosquito, my libido").sender(bob).event_id(event_id!("$7")).into_sync(), + f.text_msg("A mosquito, my libido").sender(bob).event_id(event_id!("$7")).into_event(), ); let mut selector = ReceiptSelector::new(&events, None); @@ -1524,15 +1525,15 @@ mod tests { f.text_msg("A mulatto, an albino") .sender(user_id) .event_id(event_id!("$6")) - .into_sync(), + .into_event(), ); // And others by Bob, events.push_back( - f.text_msg("A mosquito, my libido").sender(bob).event_id(event_id!("$7")).into_sync(), + f.text_msg("A mosquito, my libido").sender(bob).event_id(event_id!("$7")).into_event(), ); events.push_back( - f.text_msg("A denial, a denial").sender(bob).event_id(event_id!("$8")).into_sync(), + f.text_msg("A denial, a denial").sender(bob).event_id(event_id!("$8")).into_event(), ); let events: Vec<_> = events.into_iter().collect(); diff --git a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs index a31ac8dbb..1284fdf2b 100644 --- a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs @@ -894,7 +894,7 @@ mod tests { .event_id(original_event_id) .bundled_relations(relations) .server_ts(42) - .into_sync(); + .into_event(); let client = logged_in_client(None).await; @@ -950,7 +950,7 @@ mod tests { .event_id(original_event_id) .bundled_relations(relations) .sender(user_id) - .into_sync(); + .into_event(); let client = logged_in_client(None).await; diff --git a/crates/matrix-sdk-ui/src/timeline/tests/basic.rs b/crates/matrix-sdk-ui/src/timeline/tests/basic.rs index 43126c023..523835056 100644 --- a/crates/matrix-sdk-ui/src/timeline/tests/basic.rs +++ b/crates/matrix-sdk-ui/src/timeline/tests/basic.rs @@ -89,7 +89,7 @@ async fn test_replace_with_initial_events_and_read_marker() { .with_settings(TimelineSettings { track_read_receipts: true, ..Default::default() }); let f = &timeline.factory; - let ev = f.text_msg("hey").sender(*ALICE).into_sync(); + let ev = f.text_msg("hey").sender(*ALICE).into_event(); timeline .controller @@ -104,7 +104,7 @@ async fn test_replace_with_initial_events_and_read_marker() { assert!(items[0].is_date_divider()); assert_eq!(items[1].as_event().unwrap().content().as_message().unwrap().body(), "hey"); - let ev = f.text_msg("yo").sender(*BOB).into_sync(); + let ev = f.text_msg("yo").sender(*BOB).into_event(); timeline .controller .replace_with_initial_remote_events([ev].into_iter(), RemoteEventOrigin::Sync) @@ -276,9 +276,9 @@ async fn test_internal_id_prefix() { let timeline = TestTimeline::with_internal_id_prefix("le_prefix_".to_owned()); let f = &timeline.factory; - let ev_a = f.text_msg("A").sender(*ALICE).into_sync(); - let ev_b = f.text_msg("B").sender(*BOB).into_sync(); - let ev_c = f.text_msg("C").sender(*CAROL).into_sync(); + let ev_a = f.text_msg("A").sender(*ALICE).into_event(); + let ev_b = f.text_msg("B").sender(*BOB).into_event(); + let ev_c = f.text_msg("C").sender(*CAROL).into_event(); timeline .controller @@ -445,7 +445,7 @@ async fn test_replace_with_initial_events_when_batched() { .with_settings(TimelineSettings::default()); let f = &timeline.factory; - let ev = f.text_msg("hey").sender(*ALICE).into_sync(); + let ev = f.text_msg("hey").sender(*ALICE).into_event(); timeline .controller @@ -460,7 +460,7 @@ async fn test_replace_with_initial_events_when_batched() { assert!(items[0].is_date_divider()); assert_eq!(items[1].as_event().unwrap().content().as_message().unwrap().body(), "hey"); - let ev = f.text_msg("yo").sender(*BOB).into_sync(); + let ev = f.text_msg("yo").sender(*BOB).into_event(); timeline .controller .replace_with_initial_remote_events([ev].into_iter(), RemoteEventOrigin::Sync) diff --git a/crates/matrix-sdk-ui/src/timeline/tests/echo.rs b/crates/matrix-sdk-ui/src/timeline/tests/echo.rs index f5a85de44..8d097f7d8 100644 --- a/crates/matrix-sdk-ui/src/timeline/tests/echo.rs +++ b/crates/matrix-sdk-ui/src/timeline/tests/echo.rs @@ -251,7 +251,7 @@ async fn test_no_read_marker_with_local_echo() { .sender(user_id!("@a:b.c")) .event_id(event_id) .server_ts(MilliSecondsSinceUnixEpoch::now()) - .into_sync()] + .into_event()] .into_iter(), RemoteEventOrigin::Sync, ) diff --git a/crates/matrix-sdk-ui/src/timeline/tests/reactions.rs b/crates/matrix-sdk-ui/src/timeline/tests/reactions.rs index 9f42e1013..78ca0d69b 100644 --- a/crates/matrix-sdk-ui/src/timeline/tests/reactions.rs +++ b/crates/matrix-sdk-ui/src/timeline/tests/reactions.rs @@ -198,9 +198,9 @@ async fn test_initial_reaction_timestamp_is_stored() { // Reaction comes first. f.reaction(&message_event_id, REACTION_KEY) .server_ts(reaction_timestamp) - .into_sync(), + .into_event(), // Event comes next. - f.text_msg("A").event_id(&message_event_id).into_sync(), + f.text_msg("A").event_id(&message_event_id).into_event(), ] .into_iter(), TimelineNewItemPosition::End { origin: RemoteEventOrigin::Sync }, diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/focus_event.rs b/crates/matrix-sdk-ui/tests/integration/timeline/focus_event.rs index ee7a3b3e6..47569fa5c 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/focus_event.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/focus_event.rs @@ -54,13 +54,13 @@ async fn test_new_focused() { target_event, Some("prev1".to_owned()), vec![ - f.text_msg("i tried so hard").sender(*ALICE).into_sync(), - f.text_msg("and got so far").sender(*ALICE).into_sync(), + f.text_msg("i tried so hard").sender(*ALICE).into_event(), + f.text_msg("and got so far").sender(*ALICE).into_event(), ], - f.text_msg("in the end").event_id(target_event).sender(*BOB).into_sync(), + f.text_msg("in the end").event_id(target_event).sender(*BOB).into_event(), vec![ - f.text_msg("it doesn't even").sender(*ALICE).into_sync(), - f.text_msg("matter").sender(*ALICE).into_sync(), + f.text_msg("it doesn't even").sender(*ALICE).into_event(), + f.text_msg("matter").sender(*ALICE).into_event(), ], Some("next1".to_owned()), vec![], @@ -114,8 +114,8 @@ async fn test_new_focused() { None, vec![ // reversed manually here - f.text_msg("And even though I tried, it all fell apart").sender(*BOB).into_sync(), - f.text_msg("I kept everything inside").sender(*BOB).into_sync(), + f.text_msg("And even though I tried, it all fell apart").sender(*BOB).into_event(), + f.text_msg("I kept everything inside").sender(*BOB).into_event(), ], vec![], ) @@ -154,8 +154,8 @@ async fn test_new_focused() { "next1".to_owned(), Some("next2".to_owned()), vec![ - f.text_msg("I had to fall, to lose it all").sender(*BOB).into_sync(), - f.text_msg("But in the end, it doesn't event matter").sender(*BOB).into_sync(), + f.text_msg("I had to fall, to lose it all").sender(*BOB).into_event(), + f.text_msg("But in the end, it doesn't event matter").sender(*BOB).into_event(), ], vec![], ) @@ -208,7 +208,7 @@ async fn test_focused_timeline_reacts() { target_event, None, vec![], - f.text_msg("yolo").event_id(target_event).sender(*BOB).into_sync(), + f.text_msg("yolo").event_id(target_event).sender(*BOB).into_event(), vec![], None, vec![], @@ -293,7 +293,7 @@ async fn test_focused_timeline_local_echoes() { target_event, None, vec![], - f.text_msg("yolo").event_id(target_event).sender(*BOB).into_sync(), + f.text_msg("yolo").event_id(target_event).sender(*BOB).into_event(), vec![], None, vec![], @@ -372,7 +372,7 @@ async fn test_focused_timeline_doesnt_show_local_echoes() { target_event, None, vec![], - f.text_msg("yolo").event_id(target_event).sender(*BOB).into_sync(), + f.text_msg("yolo").event_id(target_event).sender(*BOB).into_event(), vec![], None, vec![], diff --git a/crates/matrix-sdk/src/event_cache/deduplicator.rs b/crates/matrix-sdk/src/event_cache/deduplicator.rs index 9714ab516..d84807f37 100644 --- a/crates/matrix-sdk/src/event_cache/deduplicator.rs +++ b/crates/matrix-sdk/src/event_cache/deduplicator.rs @@ -166,7 +166,7 @@ mod tests { .text_msg("") .sender(user_id!("@mnt_io:matrix.org")) .event_id(event_id) - .into_sync() + .into_event() } #[test] diff --git a/crates/matrix-sdk/src/event_cache/pagination.rs b/crates/matrix-sdk/src/event_cache/pagination.rs index a0ae902a5..325e1e7ff 100644 --- a/crates/matrix-sdk/src/event_cache/pagination.rs +++ b/crates/matrix-sdk/src/event_cache/pagination.rs @@ -453,8 +453,9 @@ mod tests { .write() .await .with_events_mut(|events| { - events - .push_events([f.text_msg("this is the start of the timeline").into_sync()]); + events.push_events([f + .text_msg("this is the start of the timeline") + .into_event()]); }) .await .unwrap(); @@ -498,7 +499,7 @@ mod tests { .with_events_mut(|events| { events.push_events([f .text_msg("this is the start of the timeline") - .into_sync()]); + .into_event()]); }) .await .unwrap(); @@ -542,7 +543,7 @@ mod tests { .text_msg("yolo") .sender(user_id!("@b:z.h")) .event_id(event_id!("$ida")) - .into_sync()]); + .into_event()]); }) .await .unwrap(); diff --git a/crates/matrix-sdk/src/event_cache/paginator.rs b/crates/matrix-sdk/src/event_cache/paginator.rs index fa445f3cf..599faf19c 100644 --- a/crates/matrix-sdk/src/event_cache/paginator.rs +++ b/crates/matrix-sdk/src/event_cache/paginator.rs @@ -609,7 +609,7 @@ mod tests { .event_factory .text_msg(self.target_event_text.lock().await.clone()) .event_id(event_id) - .into_sync(); + .into_event(); // Properly simulate `num_events`: take either the closest num_events events // before, or use all of the before events and then consume after events. @@ -707,10 +707,10 @@ mod tests { *room.target_event_text.lock().await = "fetch_from".to_owned(); *room.prev_events.lock().await = (0..10) .rev() - .map(|i| event_factory.text_msg(format!("before-{i}")).into_sync()) + .map(|i| event_factory.text_msg(format!("before-{i}")).into_event()) .collect(); *room.next_events.lock().await = - (0..10).map(|i| event_factory.text_msg(format!("after-{i}")).into_sync()).collect(); + (0..10).map(|i| event_factory.text_msg(format!("after-{i}")).into_event()).collect(); // When I call `Paginator::start_from`, it works, let paginator = Arc::new(Paginator::new(room.clone())); @@ -747,7 +747,7 @@ mod tests { *room.target_event_text.lock().await = "fetch_from".to_owned(); *room.prev_events.lock().await = - (0..100).rev().map(|i| event_factory.text_msg(format!("ev{i}")).into_sync()).collect(); + (0..100).rev().map(|i| event_factory.text_msg(format!("ev{i}")).into_event()).collect(); // When I call `Paginator::start_from`, it works, let paginator = Arc::new(Paginator::new(room.clone())); @@ -800,7 +800,7 @@ mod tests { assert!(paginator.hit_timeline_end()); // Preparing data for the next back-pagination. - *room.prev_events.lock().await = vec![event_factory.text_msg("previous").into_sync()]; + *room.prev_events.lock().await = vec![event_factory.text_msg("previous").into_event()]; *room.prev_batch_token.lock().await = Some("prev2".to_owned()); // When I backpaginate, I get the events I expect. @@ -813,7 +813,7 @@ mod tests { // And I can backpaginate again, because there's a prev batch token // still. - *room.prev_events.lock().await = vec![event_factory.text_msg("oldest").into_sync()]; + *room.prev_events.lock().await = vec![event_factory.text_msg("oldest").into_event()]; *room.prev_batch_token.lock().await = None; let prev = paginator @@ -864,7 +864,7 @@ mod tests { // Preparing data for the next back-pagination. *room.prev_events.lock().await = (0..100) .rev() - .map(|i| event_factory.text_msg(format!("prev{i}")).into_sync()) + .map(|i| event_factory.text_msg(format!("prev{i}")).into_event()) .collect(); *room.prev_batch_token.lock().await = None; @@ -914,7 +914,7 @@ mod tests { assert!(!paginator.hit_timeline_end()); // Preparing data for the next forward-pagination. - *room.next_events.lock().await = vec![event_factory.text_msg("next").into_sync()]; + *room.next_events.lock().await = vec![event_factory.text_msg("next").into_event()]; *room.next_batch_token.lock().await = Some("next2".to_owned()); // When I forward-paginate, I get the events I expect. @@ -927,7 +927,7 @@ mod tests { // And I can forward-paginate again, because there's a prev batch token // still. - *room.next_events.lock().await = vec![event_factory.text_msg("latest").into_sync()]; + *room.next_events.lock().await = vec![event_factory.text_msg("latest").into_event()]; *room.next_batch_token.lock().await = None; let next = paginator diff --git a/crates/matrix-sdk/src/event_cache/room/events.rs b/crates/matrix-sdk/src/event_cache/room/events.rs index 142594ecb..1b3a6cec5 100644 --- a/crates/matrix-sdk/src/event_cache/room/events.rs +++ b/crates/matrix-sdk/src/event_cache/room/events.rs @@ -557,7 +557,7 @@ mod tests { .text_msg("") .sender(user_id!("@mnt_io:matrix.org")) .event_id(&event_id) - .into_sync(); + .into_event(); (event_id, event) } diff --git a/crates/matrix-sdk/src/event_cache/room/mod.rs b/crates/matrix-sdk/src/event_cache/room/mod.rs index c48bcbdf6..3471f4a57 100644 --- a/crates/matrix-sdk/src/event_cache/room/mod.rs +++ b/crates/matrix-sdk/src/event_cache/room/mod.rs @@ -805,8 +805,8 @@ mod private { content: ChunkContent::Items(vec![ f.text_msg("hey") .event_id(event_id!("$123456789101112131415617181920")) - .into_sync(), - f.text_msg("you").event_id(event_id!("$2")).into_sync(), + .into_event(), + f.text_msg("you").event_id(event_id!("$2")).into_event(), ]), identifier: CId::new(1), previous: Some(CId::new(0)), @@ -1125,7 +1125,7 @@ mod tests { let timeline = Timeline { limited: true, prev_batch: Some("raclette".to_owned()), - events: vec![f.text_msg("hey yo").sender(*ALICE).into_sync()], + events: vec![f.text_msg("hey yo").sender(*ALICE).into_event()], }; room_event_cache @@ -1197,7 +1197,7 @@ mod tests { let mut relations = BundledMessageLikeRelations::new(); relations.replace = Some(Box::new(f.text_msg("Hello, Kind Sir").sender(*ALICE).into_raw_sync())); - let ev = f.text_msg("hey yo").sender(*ALICE).bundled_relations(relations).into_sync(); + let ev = f.text_msg("hey yo").sender(*ALICE).bundled_relations(relations).into_event(); let timeline = Timeline { limited: false, prev_batch: None, events: vec![ev] }; @@ -1261,8 +1261,8 @@ mod tests { let event_id1 = event_id!("$1"); let event_id2 = event_id!("$2"); - let ev1 = f.text_msg("hello world").sender(*ALICE).event_id(event_id1).into_sync(); - let ev2 = f.text_msg("how's it going").sender(*BOB).event_id(event_id2).into_sync(); + let ev1 = f.text_msg("hello world").sender(*ALICE).event_id(event_id1).into_event(); + let ev2 = f.text_msg("how's it going").sender(*BOB).event_id(event_id2).into_event(); // Prefill the store with some data. event_cache_store @@ -1372,8 +1372,8 @@ mod tests { let event_id1 = event_id!("$1"); let event_id2 = event_id!("$2"); - let ev1 = f.text_msg("hello world").sender(*ALICE).event_id(event_id1).into_sync(); - let ev2 = f.text_msg("how's it going").sender(*BOB).event_id(event_id2).into_sync(); + let ev1 = f.text_msg("hello world").sender(*ALICE).event_id(event_id1).into_event(); + let ev2 = f.text_msg("how's it going").sender(*BOB).event_id(event_id2).into_event(); // Prefill the store with some data. event_cache_store @@ -1547,7 +1547,7 @@ mod tests { timeline: Timeline { limited: true, prev_batch: Some("raclette".to_owned()), - events: vec![f.text_msg("hey yo").into_sync()], + events: vec![f.text_msg("hey yo").into_event()], }, ..Default::default() }, @@ -1583,7 +1583,7 @@ mod tests { timeline: Timeline { limited: false, prev_batch: Some("fondue".to_owned()), - events: vec![f.text_msg("sup").into_sync()], + events: vec![f.text_msg("sup").into_event()], }, ..Default::default() }, diff --git a/crates/matrix-sdk/src/room/edit.rs b/crates/matrix-sdk/src/room/edit.rs index 33fe5dbeb..0b3844afe 100644 --- a/crates/matrix-sdk/src/room/edit.rs +++ b/crates/matrix-sdk/src/room/edit.rs @@ -589,7 +589,7 @@ mod tests { .caption(Some("caption".to_owned()), None) .event_id(event_id) .sender(own_user_id) - .into_sync(); + .into_event(); { // Sanity checks. @@ -648,7 +648,7 @@ mod tests { .image(filename.to_owned(), owned_mxc_uri!("mxc://sdk.rs/rickroll")) .event_id(event_id) .sender(own_user_id) - .into_sync(); + .into_event(); { // Sanity checks. diff --git a/crates/matrix-sdk/tests/integration/room/joined.rs b/crates/matrix-sdk/tests/integration/room/joined.rs index b2db9fd14..a55114464 100644 --- a/crates/matrix-sdk/tests/integration/room/joined.rs +++ b/crates/matrix-sdk/tests/integration/room/joined.rs @@ -797,7 +797,7 @@ async fn test_make_reply_event_doesnt_require_event_cache() { let event_id = event_id!("$1"); let f = EventFactory::new(); mock.mock_room_event() - .ok(f.text_msg("hi").event_id(event_id).sender(&user_id).room(room_id).into_sync()) + .ok(f.text_msg("hi").event_id(event_id).sender(&user_id).room(room_id).into_event()) .expect(1) .named("/event") .mount() diff --git a/crates/matrix-sdk/tests/integration/send_queue.rs b/crates/matrix-sdk/tests/integration/send_queue.rs index ceb64e419..392448736 100644 --- a/crates/matrix-sdk/tests/integration/send_queue.rs +++ b/crates/matrix-sdk/tests/integration/send_queue.rs @@ -903,7 +903,7 @@ async fn test_edit() { .text_msg("msg1") .sender(client.user_id().unwrap()) .room(room_id) - .into_sync()) + .into_event()) .expect(1) .named("room_event") .mount() @@ -1010,7 +1010,7 @@ async fn test_edit_with_poll_start() { .poll_start("poll_start", "question", vec!["Answer A"]) .sender(client.user_id().unwrap()) .room(room_id) - .into_sync()) + .into_event()) .expect(1) .named("get_event") .mount() @@ -2944,7 +2944,7 @@ async fn test_update_caption_while_sending_media_event() { .image("surprise.jpeg.exe".to_owned(), owned_mxc_uri!("mxc://sdk.rs/media")) .sender(client.user_id().unwrap()) .room(room_id) - .into_sync()) + .into_event()) .expect(1) .named("room_event") .mount() diff --git a/testing/matrix-sdk-test/src/event_factory.rs b/testing/matrix-sdk-test/src/event_factory.rs index 9b6f3e59a..61ea252e0 100644 --- a/testing/matrix-sdk-test/src/event_factory.rs +++ b/testing/matrix-sdk-test/src/event_factory.rs @@ -254,7 +254,7 @@ where Raw::new(&self.construct_json(false)).unwrap().cast() } - pub fn into_sync(self) -> TimelineEvent { + pub fn into_event(self) -> TimelineEvent { TimelineEvent::new(self.into_raw_sync()) } } @@ -359,7 +359,7 @@ where E::EventType: Serialize, { fn from(val: EventBuilder) -> Self { - val.into_sync() + val.into_event() } } From 2d0f8733421cc5cfcb50f0dde838a552ab2e2036 Mon Sep 17 00:00:00 2001 From: Benjamin Bouvier Date: Tue, 21 Jan 2025 16:09:34 +0100 Subject: [PATCH 13/55] refactor: send respects to multiple automated lints and checks --- .../src/deserialized_responses.rs | 2 ++ .../matrix-sdk-ui/src/timeline/controller/state.rs | 4 ++-- .../src/timeline/pinned_events_loader.rs | 2 +- crates/matrix-sdk/src/room/mod.rs | 14 +++++++++----- crates/matrix-sdk/src/sliding_sync/room.rs | 7 ++----- 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/crates/matrix-sdk-common/src/deserialized_responses.rs b/crates/matrix-sdk-common/src/deserialized_responses.rs index 54366b475..0a25b5393 100644 --- a/crates/matrix-sdk-common/src/deserialized_responses.rs +++ b/crates/matrix-sdk-common/src/deserialized_responses.rs @@ -14,6 +14,8 @@ use std::{collections::BTreeMap, fmt}; +#[cfg(doc)] +use ruma::events::AnyTimelineEvent; use ruma::{ events::{AnyMessageLikeEvent, AnySyncTimelineEvent}, push::Action, diff --git a/crates/matrix-sdk-ui/src/timeline/controller/state.rs b/crates/matrix-sdk-ui/src/timeline/controller/state.rs index 18fa85cbe..75bd8512f 100644 --- a/crates/matrix-sdk-ui/src/timeline/controller/state.rs +++ b/crates/matrix-sdk-ui/src/timeline/controller/state.rs @@ -279,7 +279,7 @@ impl TimelineState { let handle_one_res = txn .handle_remote_event( - event.into(), + event, TimelineItemPosition::UpdateAt { timeline_item_index: idx }, room_data_provider, settings, @@ -759,7 +759,7 @@ impl TimelineStateTransaction<'_> { }, is_highlighted: push_actions .as_ref() - .map_or(false, |actions| actions.iter().any(Action::is_highlight)), + .is_some_and(|actions| actions.iter().any(Action::is_highlight)), flow: Flow::Remote { event_id: event_id.clone(), raw_event: raw, diff --git a/crates/matrix-sdk-ui/src/timeline/pinned_events_loader.rs b/crates/matrix-sdk-ui/src/timeline/pinned_events_loader.rs index 53b844411..28a9ed9a7 100644 --- a/crates/matrix-sdk-ui/src/timeline/pinned_events_loader.rs +++ b/crates/matrix-sdk-ui/src/timeline/pinned_events_loader.rs @@ -163,7 +163,7 @@ impl PinnedEventsRoom for Room { debug!("Loading pinned event {event_id} from HS"); self.event(event_id, request_config) .await - .map(|e| (e.into(), Vec::new())) + .map(|e| (e, Vec::new())) .map_err(|err| PaginatorError::SdkError(Box::new(err))) }) } diff --git a/crates/matrix-sdk/src/room/mod.rs b/crates/matrix-sdk/src/room/mod.rs index df74cffb3..c9b60222c 100644 --- a/crates/matrix-sdk/src/room/mod.rs +++ b/crates/matrix-sdk/src/room/mod.rs @@ -326,7 +326,11 @@ impl Room { start: http_response.start, end: http_response.end, #[cfg(not(feature = "e2e-encryption"))] - chunk: http_response.chunk.into_iter().map(TimelineEvent::new).collect(), + chunk: http_response + .chunk + .into_iter() + .map(|raw| TimelineEvent::new(raw.cast())) + .collect(), #[cfg(feature = "e2e-encryption")] chunk: Vec::with_capacity(http_response.chunk.len()), state: http_response.state, @@ -481,7 +485,7 @@ impl Room { // Save the event into the event cache, if it's set up. if let Ok((cache, _handles)) = self.event_cache().await { - cache.save_event(event.clone().into()).await; + cache.save_event(event.clone()).await; } Ok(event) @@ -527,15 +531,15 @@ impl Room { if let Ok((cache, _handles)) = self.event_cache().await { let mut events_to_save: Vec = Vec::new(); if let Some(event) = &target_event { - events_to_save.push(event.clone().into()); + events_to_save.push(event.clone()); } for event in &events_before { - events_to_save.push(event.clone().into()); + events_to_save.push(event.clone()); } for event in &events_after { - events_to_save.push(event.clone().into()); + events_to_save.push(event.clone()); } cache.save_events(events_to_save).await; diff --git a/crates/matrix-sdk/src/sliding_sync/room.rs b/crates/matrix-sdk/src/sliding_sync/room.rs index 4615fb1d7..49ce04a0f 100644 --- a/crates/matrix-sdk/src/sliding_sync/room.rs +++ b/crates/matrix-sdk/src/sliding_sync/room.rs @@ -325,7 +325,7 @@ mod tests { })) .unwrap() .cast() - ).into() + ) }; } @@ -616,8 +616,7 @@ mod tests { })) .unwrap() .cast(), - ) - .into()], + )], }; let serialized = serde_json::to_value(&frozen_room).unwrap(); @@ -669,7 +668,6 @@ mod tests { .unwrap() .cast(), ) - .into() }) .collect::>(); @@ -706,7 +704,6 @@ mod tests { .unwrap() .cast(), ) - .into() }) .collect::>(); From 4c4dd0341142e55bd67b1a70ff48e6fec340a4e9 Mon Sep 17 00:00:00 2001 From: maan2003 <49202620+maan2003@users.noreply.github.com> Date: Thu, 23 Jan 2025 14:27:11 +0530 Subject: [PATCH 14/55] fix(wasm): don't use tokio::time::{timeout,sleep} (#4573) Tokio timeout and sleep don't work on wasm so provide alternative versions --------- Signed-off-by: Manmeet Singh Signed-off-by: Andy Balaam Co-authored-by: Andy Balaam --- crates/matrix-sdk-common/src/lib.rs | 1 + crates/matrix-sdk-common/src/sleep.rs | 49 +++++++++++++++++++ crates/matrix-sdk-common/src/store_locks.rs | 3 +- crates/matrix-sdk-common/src/timeout.rs | 4 +- .../src/encryption_sync_service.rs | 4 +- .../matrix-sdk-ui/src/notification_client.rs | 6 ++- .../src/room_list_service/mod.rs | 7 ++- .../src/unable_to_decrypt_hook.rs | 6 +-- .../matrix-sdk/src/encryption/backups/mod.rs | 9 ++-- crates/matrix-sdk/src/encryption/tasks.rs | 2 +- .../matrix-sdk/src/event_cache/pagination.rs | 5 +- crates/matrix-sdk/src/sync.rs | 7 +-- labs/multiverse/src/main.rs | 3 +- 13 files changed, 75 insertions(+), 31 deletions(-) create mode 100644 crates/matrix-sdk-common/src/sleep.rs diff --git a/crates/matrix-sdk-common/src/lib.rs b/crates/matrix-sdk-common/src/lib.rs index 78c2b2d43..21ef01e59 100644 --- a/crates/matrix-sdk-common/src/lib.rs +++ b/crates/matrix-sdk-common/src/lib.rs @@ -28,6 +28,7 @@ pub mod failures_cache; pub mod linked_chunk; pub mod locks; pub mod ring_buffer; +pub mod sleep; pub mod store_locks; pub mod timeout; pub mod tracing_timer; diff --git a/crates/matrix-sdk-common/src/sleep.rs b/crates/matrix-sdk-common/src/sleep.rs new file mode 100644 index 000000000..2aebaa90f --- /dev/null +++ b/crates/matrix-sdk-common/src/sleep.rs @@ -0,0 +1,49 @@ +// Copyright 2024 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::time::Duration; + +/// Sleep for the specified duration. +/// +/// This is a cross-platform sleep implementation that works on both wasm32 and +/// non-wasm32 targets. +pub async fn sleep(duration: Duration) { + #[cfg(not(target_arch = "wasm32"))] + tokio::time::sleep(duration).await; + + #[cfg(target_arch = "wasm32")] + gloo_timers::future::TimeoutFuture::new(u32::try_from(duration.as_millis()).unwrap_or_else( + |_| { + tracing::error!("Sleep duration too long, sleeping for u32::MAX ms"); + u32::MAX + }, + )) + .await; +} + +#[cfg(test)] +mod tests { + use matrix_sdk_test_macros::async_test; + + use super::*; + + #[cfg(target_arch = "wasm32")] + wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + + #[async_test] + async fn test_sleep() { + // Just test that it doesn't panic + sleep(Duration::from_millis(1)).await; + } +} diff --git a/crates/matrix-sdk-common/src/store_locks.rs b/crates/matrix-sdk-common/src/store_locks.rs index 4b55d9c59..4d680771e 100644 --- a/crates/matrix-sdk-common/src/store_locks.rs +++ b/crates/matrix-sdk-common/src/store_locks.rs @@ -46,11 +46,12 @@ use std::{ time::Duration, }; -use tokio::{sync::Mutex, time::sleep}; +use tokio::sync::Mutex; use tracing::{debug, error, info, instrument, trace}; use crate::{ executor::{spawn, JoinHandle}, + sleep::sleep, SendOutsideWasm, }; diff --git a/crates/matrix-sdk-common/src/timeout.rs b/crates/matrix-sdk-common/src/timeout.rs index c56c19406..13417c41b 100644 --- a/crates/matrix-sdk-common/src/timeout.rs +++ b/crates/matrix-sdk-common/src/timeout.rs @@ -41,7 +41,7 @@ impl Error for ElapsedError {} /// an error. pub async fn timeout(future: F, duration: Duration) -> Result where - F: Future + Unpin, + F: Future, { #[cfg(not(target_arch = "wasm32"))] return tokio_timeout(duration, future).await.map_err(|_| ElapsedError(())); @@ -51,7 +51,7 @@ where let timeout_future = TimeoutFuture::new(u32::try_from(duration.as_millis()).expect("Overlong duration")); - match select(future, timeout_future).await { + match select(std::pin::pin!(future), timeout_future).await { Either::Left((res, _)) => Ok(res), Either::Right((_, _)) => Err(ElapsedError(())), } diff --git a/crates/matrix-sdk-ui/src/encryption_sync_service.rs b/crates/matrix-sdk-ui/src/encryption_sync_service.rs index 67148fc7f..fcbf4ef2f 100644 --- a/crates/matrix-sdk-ui/src/encryption_sync_service.rs +++ b/crates/matrix-sdk-ui/src/encryption_sync_service.rs @@ -31,7 +31,7 @@ use std::{pin::Pin, time::Duration}; use async_stream::stream; use futures_core::stream::Stream; use futures_util::{pin_mut, StreamExt}; -use matrix_sdk::{Client, SlidingSync, LEASE_DURATION_MS}; +use matrix_sdk::{sleep::sleep, Client, SlidingSync, LEASE_DURATION_MS}; use matrix_sdk_base::sliding_sync::http; use ruma::assign; use tokio::sync::OwnedMutexGuard; @@ -174,7 +174,7 @@ impl EncryptionSyncService { LEASE_DURATION_MS ); - tokio::time::sleep(Duration::from_millis(LEASE_DURATION_MS.into())).await; + sleep(Duration::from_millis(LEASE_DURATION_MS.into())).await; lock_guard = self .client diff --git a/crates/matrix-sdk-ui/src/notification_client.rs b/crates/matrix-sdk-ui/src/notification_client.rs index 5af760e71..07571f406 100644 --- a/crates/matrix-sdk-ui/src/notification_client.rs +++ b/crates/matrix-sdk-ui/src/notification_client.rs @@ -18,7 +18,9 @@ use std::{ }; use futures_util::{pin_mut, StreamExt as _}; -use matrix_sdk::{room::Room, Client, ClientBuildError, SlidingSyncList, SlidingSyncMode}; +use matrix_sdk::{ + room::Room, sleep::sleep, Client, ClientBuildError, SlidingSyncList, SlidingSyncMode, +}; use matrix_sdk_base::{ deserialized_responses::TimelineEvent, sliding_sync::http, RoomState, StoreError, }; @@ -212,7 +214,7 @@ impl NotificationClient { for _ in 0..3 { trace!("waiting for decryption…"); - tokio::time::sleep(Duration::from_millis(wait)).await; + sleep(Duration::from_millis(wait)).await; let new_event = room.decrypt_event(raw_event.cast_ref()).await?; diff --git a/crates/matrix-sdk-ui/src/room_list_service/mod.rs b/crates/matrix-sdk-ui/src/room_list_service/mod.rs index 34246bcc2..a76643ad3 100644 --- a/crates/matrix-sdk-ui/src/room_list_service/mod.rs +++ b/crates/matrix-sdk-ui/src/room_list_service/mod.rs @@ -63,8 +63,8 @@ use async_stream::stream; use eyeball::Subscriber; use futures_util::{pin_mut, Stream, StreamExt}; use matrix_sdk::{ - event_cache::EventCacheError, Client, Error as SlidingSyncError, SlidingSync, SlidingSyncList, - SlidingSyncMode, + event_cache::EventCacheError, timeout::timeout, Client, Error as SlidingSyncError, SlidingSync, + SlidingSyncList, SlidingSyncMode, }; use matrix_sdk_base::sliding_sync::http; pub use room::*; @@ -72,7 +72,6 @@ pub use room_list::*; use ruma::{assign, directory::RoomTypeFilter, events::StateEventType, OwnedRoomId, RoomId, UInt}; pub use state::*; use thiserror::Error; -use tokio::time::timeout; use tracing::debug; use crate::timeline; @@ -328,7 +327,7 @@ impl RoomListService { }; // `state.next().await` has a maximum of `yield_delay` time to execute… - let next_state = match timeout(yield_delay, state.next()).await { + let next_state = match timeout(state.next(), yield_delay).await { // A new state has been received before `yield_delay` time. The new // `sync_indicator` value won't be yielded. Ok(next_state) => next_state, diff --git a/crates/matrix-sdk-ui/src/unable_to_decrypt_hook.rs b/crates/matrix-sdk-ui/src/unable_to_decrypt_hook.rs index 9cb4b3fdb..cb2adf7d7 100644 --- a/crates/matrix-sdk-ui/src/unable_to_decrypt_hook.rs +++ b/crates/matrix-sdk-ui/src/unable_to_decrypt_hook.rs @@ -27,6 +27,7 @@ use growable_bloom_filter::{GrowableBloom, GrowableBloomBuilder}; use matrix_sdk::{ crypto::types::events::UtdCause, executor::{spawn, JoinHandle}, + sleep::sleep, Client, }; use matrix_sdk_base::{StateStoreDataKey, StateStoreDataValue, StoreError}; @@ -34,10 +35,7 @@ use ruma::{ time::{Duration, Instant}, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedServerName, UserId, }; -use tokio::{ - sync::{Mutex as AsyncMutex, MutexGuard}, - time::sleep, -}; +use tokio::sync::{Mutex as AsyncMutex, MutexGuard}; use tracing::error; /// A generic interface which methods get called whenever we observe a diff --git a/crates/matrix-sdk/src/encryption/backups/mod.rs b/crates/matrix-sdk/src/encryption/backups/mod.rs index 538bc238c..d48ec2f1f 100644 --- a/crates/matrix-sdk/src/encryption/backups/mod.rs +++ b/crates/matrix-sdk/src/encryption/backups/mod.rs @@ -692,12 +692,9 @@ impl Backups { .upload_progress .set(UploadState::Uploading(new_counts)); - #[cfg(not(target_arch = "wasm32"))] - { - let delay = - self.client.inner.e2ee.backup_state.upload_delay.read().unwrap().to_owned(); - tokio::time::sleep(delay).await; - } + let delay = + self.client.inner.e2ee.backup_state.upload_delay.read().unwrap().to_owned(); + crate::sleep::sleep(delay).await; Ok(()) } diff --git a/crates/matrix-sdk/src/encryption/tasks.rs b/crates/matrix-sdk/src/encryption/tasks.rs index 5955a1ea9..55c67d386 100644 --- a/crates/matrix-sdk/src/encryption/tasks.rs +++ b/crates/matrix-sdk/src/encryption/tasks.rs @@ -216,7 +216,7 @@ impl BackupDownloadTask { ) { // Wait a bit, perhaps the room key will arrive in the meantime. #[cfg(not(test))] - tokio::time::sleep(Duration::from_millis(Self::DOWNLOAD_DELAY_MILLIS)).await; + crate::sleep::sleep(Duration::from_millis(Self::DOWNLOAD_DELAY_MILLIS)).await; // Now take the lock, and check that we still want to do a download. If we do, // keep hold of a strong reference to the `Client`. diff --git a/crates/matrix-sdk/src/event_cache/pagination.rs b/crates/matrix-sdk/src/event_cache/pagination.rs index 325e1e7ff..29c402292 100644 --- a/crates/matrix-sdk/src/event_cache/pagination.rs +++ b/crates/matrix-sdk/src/event_cache/pagination.rs @@ -17,9 +17,8 @@ use std::{future::Future, ops::ControlFlow, sync::Arc, time::Duration}; use eyeball::Subscriber; -use matrix_sdk_base::deserialized_responses::TimelineEvent; +use matrix_sdk_base::{deserialized_responses::TimelineEvent, timeout::timeout}; use matrix_sdk_common::linked_chunk::ChunkContent; -use tokio::time::timeout; use tracing::{debug, instrument, trace}; use super::{ @@ -295,7 +294,7 @@ impl RoomPagination { // Otherwise, wait for a notification that we received a previous-batch token. // Note the state lock is released while doing so, allowing other tasks to write // into the linked chunk. - let _ = timeout(wait_time, self.inner.pagination_batch_token_notifier.notified()).await; + let _ = timeout(self.inner.pagination_batch_token_notifier.notified(), wait_time).await; let mut state = self.inner.state.write().await; diff --git a/crates/matrix-sdk/src/sync.rs b/crates/matrix-sdk/src/sync.rs index 5a824ebf8..61f54ccfe 100644 --- a/crates/matrix-sdk/src/sync.rs +++ b/crates/matrix-sdk/src/sync.rs @@ -23,6 +23,7 @@ use std::{ pub use matrix_sdk_base::sync::*; use matrix_sdk_base::{ debug::{DebugInvitedRoom, DebugKnockedRoom, DebugListOfRawEventsNoId}, + sleep::sleep, sync::SyncResponse as BaseSyncResponse, }; use ruma::{ @@ -299,11 +300,7 @@ impl Client { } async fn sleep() { - #[cfg(target_arch = "wasm32")] - gloo_timers::future::TimeoutFuture::new(1_000).await; - - #[cfg(not(target_arch = "wasm32"))] - tokio::time::sleep(Duration::from_secs(1)).await; + sleep(Duration::from_secs(1)).await; } pub(crate) async fn sync_loop_helper( diff --git a/labs/multiverse/src/main.rs b/labs/multiverse/src/main.rs index aab3b626f..061eaf208 100644 --- a/labs/multiverse/src/main.rs +++ b/labs/multiverse/src/main.rs @@ -25,6 +25,7 @@ use matrix_sdk::{ events::room::message::{MessageType, RoomMessageEventContent}, MilliSecondsSinceUnixEpoch, OwnedRoomId, RoomId, }, + sleep::sleep, AuthSession, Client, ServerName, SqliteCryptoStore, SqliteEventCacheStore, SqliteStateStore, }; use matrix_sdk_ui::{ @@ -332,7 +333,7 @@ impl App { let message = self.last_status_message.clone(); self.clear_status_message = Some(spawn(async move { // Clear the status message in 4 seconds. - tokio::time::sleep(Duration::from_secs(4)).await; + sleep(Duration::from_secs(4)).await; *message.lock().unwrap() = None; })); From c6e308717d7e0e1f09380976ddfa506c9b70e1c5 Mon Sep 17 00:00:00 2001 From: Neil Johnson Date: Fri, 24 Jan 2025 09:24:04 +0000 Subject: [PATCH 15/55] update maturity and contribution call to action --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 825cd8036..565f14b4b 100644 --- a/README.md +++ b/README.md @@ -26,11 +26,9 @@ The rust-sdk consists of multiple crates that can be picked at your convenience: ## Status -The library is in an alpha state, things that are implemented generally work but -the API will change in breaking ways. +The library is considered production ready and backs multiple client implementations such as Element X [1](https://github.com/element-hq/element-x-ios)[2](https://github.com/element-hq/element-x-android) and [Fractal](https://gitlab.gnome.org/World/fractal). Client developers should feel confident to build upon it. -If you are interested in using the matrix-sdk now is the time to try it out and -provide feedback. +Development of the SDK has been primarily sponsored by Element though accepts contributions from all. ## Bindings From 66ffc3448ee17e263311c3d4747d779cc79a904d Mon Sep 17 00:00:00 2001 From: Neil Johnson Date: Fri, 24 Jan 2025 10:02:37 +0000 Subject: [PATCH 16/55] Update README.md style Signed-off-by: Neil Johnson --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 565f14b4b..bbf393296 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ The rust-sdk consists of multiple crates that can be picked at your convenience: ## Status -The library is considered production ready and backs multiple client implementations such as Element X [1](https://github.com/element-hq/element-x-ios)[2](https://github.com/element-hq/element-x-android) and [Fractal](https://gitlab.gnome.org/World/fractal). Client developers should feel confident to build upon it. +The library is considered production ready and backs multiple client implementations such as Element X [[1]](https://github.com/element-hq/element-x-ios) [[2]](https://github.com/element-hq/element-x-android) and [Fractal](https://gitlab.gnome.org/World/fractal). Client developers should feel confident to build upon it. Development of the SDK has been primarily sponsored by Element though accepts contributions from all. From 87983ab6104848e4f669c3e98d42ac9476bb55b3 Mon Sep 17 00:00:00 2001 From: Doug Date: Fri, 24 Jan 2025 16:37:27 +0000 Subject: [PATCH 17/55] chore: Remove an old todo This was already done by moving the methods into Client. --- crates/matrix-sdk/src/authentication/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/matrix-sdk/src/authentication/mod.rs b/crates/matrix-sdk/src/authentication/mod.rs index eafd9c38a..1a6fad939 100644 --- a/crates/matrix-sdk/src/authentication/mod.rs +++ b/crates/matrix-sdk/src/authentication/mod.rs @@ -14,8 +14,6 @@ //! Types and functions related to authentication in Matrix. -// TODO:(pixlwave) Move AuthenticationService from the FFI into this module. - use std::sync::Arc; use as_variant::as_variant; From 756dec264d426bf5ffa286dc8c4e78a619f6455a Mon Sep 17 00:00:00 2001 From: Kevin Boos Date: Fri, 24 Jan 2025 16:11:41 -0800 Subject: [PATCH 18/55] Upgrade `imbl`, `eyeball-im`, `eyeball-im-util` to fix bounds check A bounds check was recently relaxed in `imbl`'s `Focus::narrow()` function: https://github.com/jneem/imbl/pull/89, which fixed a bug that would cause a panic if the downstream user of `matrix-sdk-ui` attempted to narrow a focus of Timeline items using a range that included the last item in the Timeline. Example: https://github.com/project-robius/robrix/issues/330 This fix has been incorporated in `eyeball-im` and `eyeball-im-util` and has been tested by me to no longer trigger upon the aforementioned conditions. --- Cargo.lock | 18 +++++++++--------- Cargo.toml | 6 +++--- .../src/room_list_service/room_list.rs | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7a966b599..4359845d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1684,9 +1684,9 @@ dependencies = [ [[package]] name = "eyeball-im" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1c02432230060cae0621e15803e073976d22974e0f013c9cb28a4ea1b484629" +checksum = "ad276eb017655257443d34f27455f60e8b02b839c6ebcaa8d6f06cc498784e8f" dependencies = [ "futures-core", "imbl", @@ -1696,9 +1696,9 @@ dependencies = [ [[package]] name = "eyeball-im-util" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a70e454238b5f66a0a0544c3e6a38be765cb01f34da9b94a2f3ecd8777cf8" +checksum = "eac7f06ce388e4f64876ad3836b275d0972ab64ae8bd8456862d5ebdb7bec4f5" dependencies = [ "arrayvec", "eyeball-im", @@ -2476,9 +2476,9 @@ dependencies = [ [[package]] name = "imbl" -version = "3.0.0" +version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc3be8d8cd36f33a46b1849f31f837c44d9fa87223baee3b4bd96b8f11df81eb" +checksum = "5ae128b3bc67ed43ec0a7bb1c337a9f026717628b3c4033f07ded1da3e854951" dependencies = [ "bitmaps", "imbl-sized-chunks", @@ -2490,9 +2490,9 @@ dependencies = [ [[package]] name = "imbl-sized-chunks" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "144006fb58ed787dcae3f54575ff4349755b00ccc99f4b4873860b654be1ed63" +checksum = "8f4241005618a62f8d57b2febd02510fb96e0137304728543dfc5fd6f052c22d" dependencies = [ "bitmaps", ] @@ -4376,7 +4376,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "157c5a9d7ea5c2ed2d9fb8f495b64759f7816c7eaea54ba3978f0d63000162e3" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.13.0", "proc-macro2", "quote", "syn", diff --git a/Cargo.toml b/Cargo.toml index d989ceae5..bd5cdb4ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,8 +34,8 @@ base64 = "0.22.1" byteorder = "1.5.0" chrono = "0.4.38" eyeball = { version = "0.8.8", features = ["tracing"] } -eyeball-im = { version = "0.5.1", features = ["tracing"] } -eyeball-im-util = "0.7.0" +eyeball-im = { version = "0.6.0", features = ["tracing"] } +eyeball-im-util = "0.8.0" futures-core = "0.3.31" futures-executor = "0.3.21" futures-util = "0.3.31" @@ -44,7 +44,7 @@ growable-bloom-filter = "2.1.1" hkdf = "0.12.4" hmac = "0.12.1" http = "1.1.0" -imbl = "3.0.0" +imbl = "4.0.1" indexmap = "2.6.0" insta = { version = "1.41.1", features = ["json"] } itertools = "0.13.0" diff --git a/crates/matrix-sdk-ui/src/room_list_service/room_list.rs b/crates/matrix-sdk-ui/src/room_list_service/room_list.rs index 99d8fa10b..0d79ac3ca 100644 --- a/crates/matrix-sdk-ui/src/room_list_service/room_list.rs +++ b/crates/matrix-sdk-ui/src/room_list_service/room_list.rs @@ -177,7 +177,7 @@ impl RoomList { Box::new(new_sorter_recency()), Box::new(new_sorter_name()) ])) - .dynamic_limit_with_initial_value(page_size, limit_stream.clone()); + .dynamic_head_with_initial_value(page_size, limit_stream.clone()); // Clearing the stream before chaining with the real stream. yield stream::once(ready(vec![VectorDiff::Reset { values }])) From 35ad5441d3b6879802bbea48d4e0f2d5be13684f Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 22 Jan 2025 17:09:48 +0000 Subject: [PATCH 19/55] crypto: split common struct out of device collection results `split_devices_for_user` returns a superset of the results of `split_recipients_withhelds_for_user_based_on_identity`: let's reflect that in the return types so we can start to share code. Also, rename `split_recipients_withhelds_for_user_based_on_identity` to `split_devices_for_user_for_identity_based_strategy` while we are here. --- .../group_sessions/share_strategy.rs | 128 ++++++++++-------- 1 file changed, 68 insertions(+), 60 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/session_manager/group_sessions/share_strategy.rs b/crates/matrix-sdk-crypto/src/session_manager/group_sessions/share_strategy.rs index f3d6efeae..05b371376 100644 --- a/crates/matrix-sdk-crypto/src/session_manager/group_sessions/share_strategy.rs +++ b/crates/matrix-sdk-crypto/src/session_manager/group_sessions/share_strategy.rs @@ -189,37 +189,30 @@ pub(crate) async fn collect_session_recipients( error_on_verified_user_problem, ); - // If `error_on_verified_user_problem` is set, then - // `unsigned_of_verified_user` may be populated. If so, add an entry to the - // list of users with unsigned devices. - if !recipient_devices.unsigned_of_verified_user.is_empty() { - unsigned_devices_of_verified_users.insert( - user_id.to_owned(), - recipient_devices - .unsigned_of_verified_user - .into_iter() - .map(|d| d.device_id().to_owned()) - .collect(), - ); - } + match recipient_devices { + DeviceBasedRecipientDevices::UnsignedDevicesOfVerifiedUser(devices) => { + unsigned_devices_of_verified_users.insert(user_id.to_owned(), devices); + } + DeviceBasedRecipientDevices::Devices(recipient_devices) => { + // If we haven't already concluded that the session should be + // rotated for other reasons, we also need to check whether any + // of the devices in the session got deleted or blacklisted in the + // meantime. If so, we should also rotate the session. + if !should_rotate { + should_rotate = is_session_overshared_for_user( + outbound, + user_id, + &recipient_devices.allowed_devices, + ) + } - // If we haven't already concluded that the session should be - // rotated for other reasons, we also need to check whether any - // of the devices in the session got deleted or blacklisted in the - // meantime. If so, we should also rotate the session. - if !should_rotate { - should_rotate = is_session_overshared_for_user( - outbound, - user_id, - &recipient_devices.allowed_devices, - ) + devices + .entry(user_id.to_owned()) + .or_default() + .extend(recipient_devices.allowed_devices); + withheld_devices.extend(recipient_devices.denied_devices_with_code); + } } - - devices - .entry(user_id.to_owned()) - .or_default() - .extend(recipient_devices.allowed_devices); - withheld_devices.extend(recipient_devices.denied_devices_with_code); } // If `error_on_verified_user_problem` is set, then @@ -265,7 +258,7 @@ pub(crate) async fn collect_session_recipients( continue; } - let recipient_devices = split_recipients_withhelds_for_user_based_on_identity( + let recipient_devices = split_devices_for_user_for_identity_based_strategy( user_devices, &device_owner_identity, ); @@ -368,33 +361,45 @@ fn is_session_overshared_for_user( should_rotate } -/// Result type for [`split_devices_for_user`]. +/// Result type for [`split_devices_for_user_for_identity_based_strategy`] and +/// [`split_devices_for_user`]. +/// +/// A partitioning of the devices for a given user. #[derive(Default)] -struct DeviceBasedRecipientDevices { +struct RecipientDevicesForUser { /// Devices that should receive the room key. allowed_devices: Vec, /// Devices that should receive a withheld code. denied_devices_with_code: Vec<(DeviceData, WithheldCode)>, - /// Devices that should cause the transmission to fail, due to being an - /// unsigned device belonging to a verified user. Only populated by - /// [`split_devices_for_user`], when - /// `error_on_verified_user_problem` is set. - unsigned_of_verified_user: Vec, +} + +/// Result type for [`split_devices_for_user`]. +enum DeviceBasedRecipientDevices { + /// We found devices that should cause the transmission to fail, due to + /// being an unsigned device belonging to a verified user. Only + /// populated when `error_on_verified_user_problem` is set. + UnsignedDevicesOfVerifiedUser(Vec), + + /// There were no unsigned devices of verified users. + Devices(RecipientDevicesForUser), } /// Partition the list of a user's devices according to whether they should /// receive the key, for [`CollectStrategy::DeviceBasedStrategy`]. /// -/// We split the list into three buckets: +/// This function returns one of two values: /// -/// * the devices that should receive the room key. +/// * If `error_on_verified_user_problem` is set, returns a list the devices +/// that should cause the transmission to fail due to being unsigned. In this +/// case, we don't bother to return the rest of the devices, because we assume +/// transmission will fail. /// -/// * the devices that should receive a withheld code. +/// (If error_on_verified_user_problem` is unset, these devices are otherwise +/// partitioned into `allowed_devices`.) /// -/// * If `error_on_verified_user_problem` is set, the devices that should cause -/// the transmission to fail due to being unsigned. (If -/// `error_on_verified_user_problem` is unset, these devices are otherwise -/// partitioned into `allowed_devices`.) +/// * Otherwise, returns a [`RecipientDevicesForUser`] which lists, separately, +/// the devices that should receive the room key, and those that should +/// receive a withheld code. fn split_devices_for_user( user_devices: HashMap, own_identity: &Option, @@ -402,7 +407,12 @@ fn split_devices_for_user( only_allow_trusted_devices: bool, error_on_verified_user_problem: bool, ) -> DeviceBasedRecipientDevices { - let mut recipient_devices: DeviceBasedRecipientDevices = Default::default(); + let mut recipient_devices = RecipientDevicesForUser::default(); + + // We construct unsigned_devices_of_verified_users lazily, because chances are + // we won't need it. + let mut unsigned_devices_of_verified_users: Option> = None; + for d in user_devices.into_values() { if d.is_blacklisted() { recipient_devices.denied_devices_with_code.push((d, WithheldCode::Blacklisted)); @@ -419,32 +429,30 @@ fn split_devices_for_user( &d, ) { - recipient_devices.unsigned_of_verified_user.push(d) + unsigned_devices_of_verified_users + .get_or_insert_with(Vec::default) + .push(d.device_id().to_owned()) } else { recipient_devices.allowed_devices.push(d); } } - recipient_devices + + if let Some(devices) = unsigned_devices_of_verified_users { + DeviceBasedRecipientDevices::UnsignedDevicesOfVerifiedUser(devices) + } else { + DeviceBasedRecipientDevices::Devices(recipient_devices) + } } -/// Result type for [`split_recipients_withhelds_for_user_based_on_identity`]. -#[derive(Default)] -struct IdentityBasedRecipientDevices { - /// Devices that should receive the room key. - allowed_devices: Vec, - /// Devices that should receive a withheld code. - denied_devices_with_code: Vec<(DeviceData, WithheldCode)>, -} - -fn split_recipients_withhelds_for_user_based_on_identity( +fn split_devices_for_user_for_identity_based_strategy( user_devices: HashMap, device_owner_identity: &Option, -) -> IdentityBasedRecipientDevices { +) -> RecipientDevicesForUser { match device_owner_identity { None => { // withheld all the users devices, we need to have an identity for this // distribution mode - IdentityBasedRecipientDevices { + RecipientDevicesForUser { allowed_devices: Vec::default(), denied_devices_with_code: user_devices .into_values() @@ -464,7 +472,7 @@ fn split_recipients_withhelds_for_user_based_on_identity( Either::Right((d, WithheldCode::Unverified)) } }); - IdentityBasedRecipientDevices { + RecipientDevicesForUser { allowed_devices: recipients, denied_devices_with_code: withheld_recipients, } From 839fbe477c401907867a2e91e282a625333c9ae9 Mon Sep 17 00:00:00 2001 From: torrybr <16907963+torrybr@users.noreply.github.com> Date: Thu, 23 Jan 2025 14:02:25 -0500 Subject: [PATCH 20/55] feat(beacons): expose ffi functions to start, stop and subscribe --- bindings/matrix-sdk-ffi/src/lib.rs | 1 + .../matrix-sdk-ffi/src/live_location_share.rs | 32 +++++++++ bindings/matrix-sdk-ffi/src/room.rs | 67 ++++++++++++++++++- 3 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 bindings/matrix-sdk-ffi/src/live_location_share.rs diff --git a/bindings/matrix-sdk-ffi/src/lib.rs b/bindings/matrix-sdk-ffi/src/lib.rs index f4a937967..deaf91a9d 100644 --- a/bindings/matrix-sdk-ffi/src/lib.rs +++ b/bindings/matrix-sdk-ffi/src/lib.rs @@ -14,6 +14,7 @@ mod error; mod event; mod helpers; mod identity_status_change; +mod live_location_share; mod notification; mod notification_settings; mod platform; diff --git a/bindings/matrix-sdk-ffi/src/live_location_share.rs b/bindings/matrix-sdk-ffi/src/live_location_share.rs new file mode 100644 index 000000000..940fb48ca --- /dev/null +++ b/bindings/matrix-sdk-ffi/src/live_location_share.rs @@ -0,0 +1,32 @@ +// Copyright 2024 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use crate::ruma::LocationContent; +#[derive(uniffi::Record)] +pub struct LastLocation { + /// The most recent location content of the user. + pub location: LocationContent, + /// A timestamp in milliseconds since Unix Epoch on that day in local + /// time. + pub ts: u64, +} +/// Details of a users live location share. +#[derive(uniffi::Record)] +pub struct LiveLocationShare { + /// The user's last known location. + pub last_location: LastLocation, + /// The live status of the live location share. + pub(crate) is_live: bool, + /// The user ID of the person sharing their live location. + pub user_id: String, +} diff --git a/bindings/matrix-sdk-ffi/src/room.rs b/bindings/matrix-sdk-ffi/src/room.rs index 08e9fdbee..3d1c18035 100644 --- a/bindings/matrix-sdk-ffi/src/room.rs +++ b/bindings/matrix-sdk-ffi/src/room.rs @@ -37,9 +37,10 @@ use crate::{ error::{ClientError, MediaInfoError, NotYetImplemented, RoomError}, event::{MessageLikeEventType, StateEventType}, identity_status_change::IdentityStatusChange, + live_location_share::{LastLocation, LiveLocationShare}, room_info::RoomInfo, room_member::RoomMember, - ruma::{ImageInfo, Mentions, NotifyType}, + ruma::{ImageInfo, LocationContent, Mentions, NotifyType}, timeline::{ configuration::{AllowedMessageTypes, TimelineConfiguration}, ReceiptType, SendHandle, Timeline, @@ -969,6 +970,70 @@ impl Room { let visibility = self.inner.privacy_settings().get_room_visibility().await?; Ok(visibility.into()) } + + /// Start the current users live location share in the room. + pub async fn start_live_location_share(&self, duration_millis: u64) -> Result<(), ClientError> { + self.inner.start_live_location_share(duration_millis, None).await?; + Ok(()) + } + + /// Stop the current users live location share in the room. + pub async fn stop_live_location_share(&self) -> Result<(), ClientError> { + self.inner.stop_live_location_share().await.expect("Unable to stop live location share"); + Ok(()) + } + + /// Send the current users live location beacon in the room. + pub async fn send_live_location(&self, geo_uri: String) -> Result<(), ClientError> { + self.inner + .send_location_beacon(geo_uri) + .await + .expect("Unable to send live location beacon"); + Ok(()) + } + + /// Subscribes to live location shares in this room, using a `listener` to + /// be notified of the changes. + /// + /// The current live location shares will be emitted immediately when + /// subscribing, along with a [`TaskHandle`] to cancel the subscription. + pub fn subscribe_to_live_location_shares( + self: Arc, + listener: Box, + ) -> Arc { + let subscription = self.inner.observe_live_location_shares(); + let stream = subscription.subscribe(); + Arc::new(TaskHandle::new(RUNTIME.spawn(async move { + pin_mut!(stream); + while let Some(event) = stream.next().await { + let last_location = LocationContent { + body: "".to_string(), + geo_uri: event.last_location.location.uri.clone().to_string(), + description: None, + zoom_level: None, + asset: None, + }; + let beacon_info = event + .beacon_info + .expect("Live location share is missing the associated beacon_info state"); + + listener.call(vec![LiveLocationShare { + last_location: LastLocation { + location: last_location, + ts: event.last_location.ts.0.into(), + }, + is_live: beacon_info.is_live(), + user_id: event.user_id.to_string(), + }]) + } + }))) + } +} + +/// A listener for receiving new live location shares in a room. +#[matrix_sdk_ffi_macros::export(callback_interface)] +pub trait LiveLocationShareListener: Sync + Send { + fn call(&self, live_location_shares: Vec); } impl From for KnockRequest { From f336638a17492effd130c38d37de53c01499f42c Mon Sep 17 00:00:00 2001 From: torrybr <16907963+torrybr@users.noreply.github.com> Date: Thu, 23 Jan 2025 14:20:19 -0500 Subject: [PATCH 21/55] refactor: move subscribe into arc --- bindings/matrix-sdk-ffi/src/room.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/bindings/matrix-sdk-ffi/src/room.rs b/bindings/matrix-sdk-ffi/src/room.rs index 3d1c18035..757c43be3 100644 --- a/bindings/matrix-sdk-ffi/src/room.rs +++ b/bindings/matrix-sdk-ffi/src/room.rs @@ -1001,13 +1001,16 @@ impl Room { self: Arc, listener: Box, ) -> Arc { - let subscription = self.inner.observe_live_location_shares(); - let stream = subscription.subscribe(); + let room = self.inner.clone(); + Arc::new(TaskHandle::new(RUNTIME.spawn(async move { - pin_mut!(stream); - while let Some(event) = stream.next().await { + let subscription = room.observe_live_location_shares(); + let mut stream = subscription.subscribe(); + let mut pinned_stream = pin!(stream); + + while let Some(event) = pinned_stream.next().await { let last_location = LocationContent { - body: "".to_string(), + body: "".to_owned(), geo_uri: event.last_location.location.uri.clone().to_string(), description: None, zoom_level: None, From aaecbf07f299d6540395354022de90ba4fa7a429 Mon Sep 17 00:00:00 2001 From: torrybr <16907963+torrybr@users.noreply.github.com> Date: Fri, 24 Jan 2025 07:37:45 -0500 Subject: [PATCH 22/55] refactor: dont panic if beacon_info is not found --- bindings/matrix-sdk-ffi/src/room.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/bindings/matrix-sdk-ffi/src/room.rs b/bindings/matrix-sdk-ffi/src/room.rs index 757c43be3..818ac3ff3 100644 --- a/bindings/matrix-sdk-ffi/src/room.rs +++ b/bindings/matrix-sdk-ffi/src/room.rs @@ -28,7 +28,7 @@ use ruma::{ EventId, Int, OwnedDeviceId, OwnedUserId, RoomAliasId, UserId, }; use tokio::sync::RwLock; -use tracing::error; +use tracing::{error, warn}; use super::RUNTIME; use crate::{ @@ -1016,9 +1016,11 @@ impl Room { zoom_level: None, asset: None, }; - let beacon_info = event - .beacon_info - .expect("Live location share is missing the associated beacon_info state"); + + let Some(beacon_info) = event.beacon_info else { + warn!("Live location share is missing the associated beacon_info state, skipping event."); + continue; + }; listener.call(vec![LiveLocationShare { last_location: LastLocation { From 2657eb7866815c3da8317bed724b99947e1a8d3e Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Mon, 27 Jan 2025 16:00:01 +0200 Subject: [PATCH 23/55] feat(ui): expose a method for checking whether a message contains only emojis and should be boosted (use a bigger font size) (#4577) - supports only text room message types - enumerates through their body's grapheme clusters and check that every single one of them is an emoji - part of the `LazyTimelineItemProvider` so that it can be opt in --- Cargo.lock | 15 ++- bindings/matrix-sdk-ffi/src/timeline/mod.rs | 4 + crates/matrix-sdk-ui/Cargo.toml | 3 + .../src/timeline/event_item/mod.rs | 106 ++++++++++++++++++ 4 files changed, 126 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4359845d2..071342235 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1410,6 +1410,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "emojis" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99e1f1df1f181f2539bac8bf027d31ca5ffbf9e559e3f2d09413b9107b5c02f4" +dependencies = [ + "phf", +] + [[package]] name = "encode_unicode" version = "0.3.6" @@ -3505,6 +3514,7 @@ dependencies = [ "async-stream", "async_cell", "chrono", + "emojis", "eyeball", "eyeball-im", "eyeball-im-util", @@ -3531,6 +3541,7 @@ dependencies = [ "tokio-stream", "tracing", "unicode-normalization", + "unicode-segmentation", "uniffi", "wiremock", ] @@ -6149,9 +6160,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-truncate" diff --git a/bindings/matrix-sdk-ffi/src/timeline/mod.rs b/bindings/matrix-sdk-ffi/src/timeline/mod.rs index 3635e81ce..ab49adfdd 100644 --- a/bindings/matrix-sdk-ffi/src/timeline/mod.rs +++ b/bindings/matrix-sdk-ffi/src/timeline/mod.rs @@ -1322,4 +1322,8 @@ impl LazyTimelineItemProvider { fn get_send_handle(&self) -> Option> { self.0.local_echo_send_handle().map(|handle| Arc::new(SendHandle::new(handle))) } + + fn contains_only_emojis(&self) -> bool { + self.0.contains_only_emojis() + } } diff --git a/crates/matrix-sdk-ui/Cargo.toml b/crates/matrix-sdk-ui/Cargo.toml index ea518a8f7..62769b597 100644 --- a/crates/matrix-sdk-ui/Cargo.toml +++ b/crates/matrix-sdk-ui/Cargo.toml @@ -51,6 +51,9 @@ tracing = { workspace = true, features = ["attributes"] } unicode-normalization = { workspace = true } uniffi = { workspace = true, optional = true } +emojis = "0.6.4" +unicode-segmentation = "1.12.0" + [dev-dependencies] anyhow = { workspace = true } assert-json-diff = { workspace = true } diff --git a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs index 1284fdf2b..3365d5298 100644 --- a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs @@ -36,6 +36,7 @@ use ruma::{ OwnedUserId, RoomId, RoomVersionId, TransactionId, UserId, }; use tracing::warn; +use unicode_segmentation::UnicodeSegmentation; mod content; mod local; @@ -604,6 +605,69 @@ impl EventTimelineItem { pub fn local_echo_send_handle(&self) -> Option { as_variant!(self.handle(), TimelineItemHandle::Local(handle) => handle.clone()) } + + /// Some clients may want to know if a particular text message or media + /// caption contains only emojis so that they can render them bigger for + /// added effect. + /// + /// This function provides that feature with the following + /// behavior/limitations: + /// - ignores leading and trailing white spaces + /// - fails texts bigger than 5 graphemes for performance reasons + /// - checks the body only for [`MessageType::Text`] + /// - only checks the caption for [`MessageType::Audio`], + /// [`MessageType::File`], [`MessageType::Image`], and + /// [`MessageType::Video`] if present + /// - all other message types will not match + /// + /// # Examples + /// # fn render_timeline_item(timeline_item: TimelineItem) { + /// if timeline_item.contains_only_emojis() { + /// // e.g. increase the font size + /// } + /// # } + /// + /// See `test_emoji_detection` for more examples. + pub fn contains_only_emojis(&self) -> bool { + let body = match self.content() { + TimelineItemContent::Message(msg) => match msg.msgtype() { + MessageType::Text(text) => Some(text.body.as_str()), + MessageType::Audio(audio) => audio.caption(), + MessageType::File(file) => file.caption(), + MessageType::Image(image) => image.caption(), + MessageType::Video(video) => video.caption(), + _ => None, + }, + TimelineItemContent::RedactedMessage + | TimelineItemContent::Sticker(_) + | TimelineItemContent::UnableToDecrypt(_) + | TimelineItemContent::MembershipChange(_) + | TimelineItemContent::ProfileChange(_) + | TimelineItemContent::OtherState(_) + | TimelineItemContent::FailedToParseMessageLike { .. } + | TimelineItemContent::FailedToParseState { .. } + | TimelineItemContent::Poll(_) + | TimelineItemContent::CallInvite + | TimelineItemContent::CallNotify => None, + }; + + if let Some(body) = body { + // Collect the graphemes after trimming white spaces. + let graphemes = body.trim().graphemes(true).collect::>(); + + // Limit the check to 5 graphemes for performance and security + // reasons. This will probably be used for every new message so we + // want it to be fast and we don't want to allow a DoS attack by + // sending a huge message. + if graphemes.len() > 5 { + return false; + } + + graphemes.iter().all(|g| emojis::get(g).is_some()) + } else { + false + } + } } impl From for EventTimelineItemKind { @@ -1060,6 +1124,48 @@ mod tests { ); } + #[async_test] + async fn test_emoji_detection() { + let room_id = room_id!("!q:x.uk"); + let user_id = user_id!("@t:o.uk"); + let client = logged_in_client(None).await; + + let mut event = message_event(room_id, user_id, "🤷‍♂️ No boost 🤷‍♂️", "", 0); + let mut timeline_item = + EventTimelineItem::from_latest_event(client.clone(), room_id, LatestEvent::new(event)) + .await + .unwrap(); + + assert!(!timeline_item.contains_only_emojis()); + + // Ignores leading and trailing white spaces + event = message_event(room_id, user_id, " 🚀 ", "", 0); + timeline_item = + EventTimelineItem::from_latest_event(client.clone(), room_id, LatestEvent::new(event)) + .await + .unwrap(); + + assert!(timeline_item.contains_only_emojis()); + + // Too many + event = message_event(room_id, user_id, "👨‍👩‍👦1️⃣🚀👳🏾‍♂️🪩👍👍🏻🫱🏼‍🫲🏾🙂👋", "", 0); + timeline_item = + EventTimelineItem::from_latest_event(client.clone(), room_id, LatestEvent::new(event)) + .await + .unwrap(); + + assert!(!timeline_item.contains_only_emojis()); + + // Works with combined emojis + event = message_event(room_id, user_id, "👨‍👩‍👦1️⃣👳🏾‍♂️👍🏻🫱🏼‍🫲🏾", "", 0); + timeline_item = + EventTimelineItem::from_latest_event(client.clone(), room_id, LatestEvent::new(event)) + .await + .unwrap(); + + assert!(timeline_item.contains_only_emojis()); + } + fn member_event( room_id: &RoomId, user_id: &UserId, From 818876a22e8b0869d46852442014487a74ac1fb0 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 22 Jan 2025 17:28:18 +0000 Subject: [PATCH 24/55] crypto: factor out common code between device collection cases Firstly, build a `CollectRecipientsResult` as we go, rather than building its components separately and then assembling it at the end. Then, factor the common code between the two code paths into a method to update the `CollectRecipientsResult`. --- .../group_sessions/share_strategy.rs | 76 +++++++++---------- 1 file changed, 34 insertions(+), 42 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/session_manager/group_sessions/share_strategy.rs b/crates/matrix-sdk-crypto/src/session_manager/group_sessions/share_strategy.rs index 05b371376..4471190d2 100644 --- a/crates/matrix-sdk-crypto/src/session_manager/group_sessions/share_strategy.rs +++ b/crates/matrix-sdk-crypto/src/session_manager/group_sessions/share_strategy.rs @@ -93,7 +93,7 @@ impl Default for CollectStrategy { /// (`should_rotate`) and the list of users/devices that should receive /// (`devices`) or not the session, including withheld reason /// `withheld_devices`. -#[derive(Debug)] +#[derive(Debug, Default)] pub(crate) struct CollectRecipientsResult { /// If true the outbound group session should be rotated pub should_rotate: bool, @@ -118,8 +118,7 @@ pub(crate) async fn collect_session_recipients( outbound: &OutboundGroupSession, ) -> OlmResult { let users: BTreeSet<&UserId> = users.collect(); - let mut devices: BTreeMap> = Default::default(); - let mut withheld_devices: Vec<(DeviceData, WithheldCode)> = Default::default(); + let mut result = CollectRecipientsResult::default(); let mut verified_users_with_new_identities: Vec = Default::default(); trace!(?users, ?settings, "Calculating group session recipients"); @@ -144,7 +143,7 @@ pub(crate) async fn collect_session_recipients( // 4. The encryption algorithm changed. // // This is calculated in the following code and stored in this variable. - let mut should_rotate = user_left || visibility_changed || algorithm_changed; + result.should_rotate = user_left || visibility_changed || algorithm_changed; let own_identity = store.get_user_identity(store.user_id()).await?.and_then(|i| i.into_own()); @@ -194,23 +193,7 @@ pub(crate) async fn collect_session_recipients( unsigned_devices_of_verified_users.insert(user_id.to_owned(), devices); } DeviceBasedRecipientDevices::Devices(recipient_devices) => { - // If we haven't already concluded that the session should be - // rotated for other reasons, we also need to check whether any - // of the devices in the session got deleted or blacklisted in the - // meantime. If so, we should also rotate the session. - if !should_rotate { - should_rotate = is_session_overshared_for_user( - outbound, - user_id, - &recipient_devices.allowed_devices, - ) - } - - devices - .entry(user_id.to_owned()) - .or_default() - .extend(recipient_devices.allowed_devices); - withheld_devices.extend(recipient_devices.denied_devices_with_code); + result.update_for_user(outbound, user_id, recipient_devices); } } } @@ -263,23 +246,7 @@ pub(crate) async fn collect_session_recipients( &device_owner_identity, ); - // If we haven't already concluded that the session should be - // rotated for other reasons, we also need to check whether any - // of the devices in the session got deleted or blacklisted in the - // meantime. If so, we should also rotate the session. - if !should_rotate { - should_rotate = is_session_overshared_for_user( - outbound, - user_id, - &recipient_devices.allowed_devices, - ) - } - - devices - .entry(user_id.to_owned()) - .or_default() - .extend(recipient_devices.allowed_devices); - withheld_devices.extend(recipient_devices.denied_devices_with_code); + update_recipients_for_user(&mut result, outbound, user_id, recipient_devices); } } } @@ -294,18 +261,43 @@ pub(crate) async fn collect_session_recipients( )); } - if should_rotate { + if result.should_rotate { debug!( - should_rotate, + result.should_rotate, user_left, visibility_changed, algorithm_changed, "Rotating room key to protect room history", ); } - trace!(should_rotate, "Done calculating group session recipients"); + trace!(result.should_rotate, "Done calculating group session recipients"); - Ok(CollectRecipientsResult { should_rotate, devices, withheld_devices }) + Ok(result) +} + +/// Update this [`CollectRecipientsResult`] with the device list for a specific +/// user. +fn update_recipients_for_user( + recipients: &mut CollectRecipientsResult, + outbound: &OutboundGroupSession, + user_id: &UserId, + recipient_devices: RecipientDevicesForUser, +) { + // If we haven't already concluded that the session should be + // rotated for other reasons, we also need to check whether any + // of the devices in the session got deleted or blacklisted in the + // meantime. If so, we should also rotate the session. + if !recipients.should_rotate { + recipients.should_rotate = + is_session_overshared_for_user(outbound, user_id, &recipient_devices.allowed_devices) + } + + recipients + .devices + .entry(user_id.to_owned()) + .or_default() + .extend(recipient_devices.allowed_devices); + recipients.withheld_devices.extend(recipient_devices.denied_devices_with_code); } /// Check if the session has been shared with a device belonging to the given From 709b09c4ec72239598ebcaa4c4b7d92ec53e68fc Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Fri, 17 Jan 2025 12:06:36 +0000 Subject: [PATCH 25/55] test: factor out test helpers in `share_strategy` tests Some helpers for creating common `EncryptionSettings` --- .../group_sessions/share_strategy.rs | 52 +++++++++---------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/session_manager/group_sessions/share_strategy.rs b/crates/matrix-sdk-crypto/src/session_manager/group_sessions/share_strategy.rs index 4471190d2..7f0753ad5 100644 --- a/crates/matrix-sdk-crypto/src/session_manager/group_sessions/share_strategy.rs +++ b/crates/matrix-sdk-crypto/src/session_manager/group_sessions/share_strategy.rs @@ -583,13 +583,7 @@ mod tests { async fn test_share_with_per_device_strategy_to_all() { let machine = set_up_test_machine().await; - let encryption_settings = EncryptionSettings { - sharing_strategy: CollectStrategy::DeviceBasedStrategy { - only_allow_trusted_devices: false, - error_on_verified_user_problem: false, - }, - ..Default::default() - }; + let encryption_settings = device_based_strategy_settings(); let group_session = create_test_outbound_group_session(&machine, &encryption_settings); @@ -1090,10 +1084,7 @@ mod tests { async fn test_share_with_identity_strategy() { let machine = set_up_test_machine().await; - let strategy = CollectStrategy::new_identity_based(); - - let encryption_settings = - EncryptionSettings { sharing_strategy: strategy.clone(), ..Default::default() }; + let encryption_settings = identity_based_strategy_settings(); let group_session = create_test_outbound_group_session(&machine, &encryption_settings); @@ -1169,10 +1160,7 @@ mod tests { let fake_room_id = room_id!("!roomid:localhost"); - let encryption_settings = EncryptionSettings { - sharing_strategy: CollectStrategy::new_identity_based(), - ..Default::default() - }; + let encryption_settings = identity_based_strategy_settings(); let request_result = machine .share_room_key( @@ -1295,10 +1283,7 @@ mod tests { let fake_room_id = room_id!("!roomid:localhost"); // We share the key using the identity-based strategy. - let encryption_settings = EncryptionSettings { - sharing_strategy: CollectStrategy::new_identity_based(), - ..Default::default() - }; + let encryption_settings = identity_based_strategy_settings(); let request_result = machine .share_room_key( @@ -1439,14 +1424,7 @@ mod tests { let machine = set_up_test_machine().await; let fake_room_id = room_id!("!roomid:localhost"); - - let strategy = CollectStrategy::DeviceBasedStrategy { - only_allow_trusted_devices: false, - error_on_verified_user_problem: false, - }; - - let encryption_settings = - EncryptionSettings { sharing_strategy: strategy.clone(), ..Default::default() }; + let encryption_settings = device_based_strategy_settings(); let requests = machine .share_room_key( @@ -1538,6 +1516,18 @@ mod tests { machine } + /// [`EncryptionSettings`] with [`CollectStrategy::DeviceBasedStrategy`] + /// (with default settings) + fn device_based_strategy_settings() -> EncryptionSettings { + EncryptionSettings { + sharing_strategy: CollectStrategy::DeviceBasedStrategy { + only_allow_trusted_devices: false, + error_on_verified_user_problem: false, + }, + ..Default::default() + } + } + /// [`EncryptionSettings`] with `error_on_verified_user_problem` set fn error_on_verification_problem_encryption_settings() -> EncryptionSettings { EncryptionSettings { @@ -1549,6 +1539,14 @@ mod tests { } } + /// [`EncryptionSettings`] with [`CollectStrategy::IdentityBasedStrategy`] + fn identity_based_strategy_settings() -> EncryptionSettings { + EncryptionSettings { + sharing_strategy: CollectStrategy::IdentityBasedStrategy, + ..Default::default() + } + } + /// Create an [`OutboundGroupSession`], backed by the given olm machine, /// without sharing it. fn create_test_outbound_group_session( From 98f4d55aa0b3568a7a818d685f39aec4fc6e9ec4 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 22 Jan 2025 11:48:28 +0000 Subject: [PATCH 26/55] test: check serialization format of `DeviceBasedStrategy` --- .../session_manager/group_sessions/share_strategy.rs | 10 ++++++++++ ...rategy__tests__serialize_device_based_strategy.snap | 5 +++++ 2 files changed, 15 insertions(+) create mode 100644 crates/matrix-sdk-crypto/src/session_manager/group_sessions/snapshots/matrix_sdk_crypto__session_manager__group_sessions__share_strategy__tests__serialize_device_based_strategy.snap diff --git a/crates/matrix-sdk-crypto/src/session_manager/group_sessions/share_strategy.rs b/crates/matrix-sdk-crypto/src/session_manager/group_sessions/share_strategy.rs index 7f0753ad5..ce954e559 100644 --- a/crates/matrix-sdk-crypto/src/session_manager/group_sessions/share_strategy.rs +++ b/crates/matrix-sdk-crypto/src/session_manager/group_sessions/share_strategy.rs @@ -517,6 +517,7 @@ mod tests { use assert_matches::assert_matches; use assert_matches2::assert_let; + use insta::assert_snapshot; use matrix_sdk_common::deserialized_responses::WithheldCode; use matrix_sdk_test::{ async_test, test_json, @@ -579,6 +580,15 @@ mod tests { machine } + /// Assert that [`CollectStrategy::DeviceBasedStrategy`] retains the same + /// serialization format. + #[test] + fn test_serialize_device_based_strategy() { + let encryption_settings = device_based_strategy_settings(); + let serialized = serde_json::to_string(&encryption_settings).unwrap(); + assert_snapshot!(serialized); + } + #[async_test] async fn test_share_with_per_device_strategy_to_all() { let machine = set_up_test_machine().await; diff --git a/crates/matrix-sdk-crypto/src/session_manager/group_sessions/snapshots/matrix_sdk_crypto__session_manager__group_sessions__share_strategy__tests__serialize_device_based_strategy.snap b/crates/matrix-sdk-crypto/src/session_manager/group_sessions/snapshots/matrix_sdk_crypto__session_manager__group_sessions__share_strategy__tests__serialize_device_based_strategy.snap new file mode 100644 index 000000000..0b1ffeaf8 --- /dev/null +++ b/crates/matrix-sdk-crypto/src/session_manager/group_sessions/snapshots/matrix_sdk_crypto__session_manager__group_sessions__share_strategy__tests__serialize_device_based_strategy.snap @@ -0,0 +1,5 @@ +--- +source: crates/matrix-sdk-crypto/src/session_manager/group_sessions/share_strategy.rs +expression: serialized +--- +{"algorithm":"m.megolm.v1.aes-sha2","rotation_period":{"secs":604800,"nanos":0},"rotation_period_msgs":100,"history_visibility":"shared","sharing_strategy":{"DeviceBasedStrategy":{"only_allow_trusted_devices":false,"error_on_verified_user_problem":false}}} From 8d612eca462c29a873fc7a2af8365bdcce472f02 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Sun, 26 Jan 2025 22:14:55 +0000 Subject: [PATCH 27/55] crypto: break up split_devices_for_user handle the separate flags with separate methods. --- .../group_sessions/share_strategy.rs | 97 ++++++++++++++++--- 1 file changed, 85 insertions(+), 12 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/session_manager/group_sessions/share_strategy.rs b/crates/matrix-sdk-crypto/src/session_manager/group_sessions/share_strategy.rs index ce954e559..4b952d120 100644 --- a/crates/matrix-sdk-crypto/src/session_manager/group_sessions/share_strategy.rs +++ b/crates/matrix-sdk-crypto/src/session_manager/group_sessions/share_strategy.rs @@ -353,8 +353,10 @@ fn is_session_overshared_for_user( should_rotate } -/// Result type for [`split_devices_for_user_for_identity_based_strategy`] and -/// [`split_devices_for_user`]. +/// Result type for [`split_devices_for_user_for_all_devices_strategy`], +/// [`split_devices_for_user_for_error_on_verified_user_problem_strategy`], +/// [`split_devices_for_user_for_identity_based_strategy`], +/// [`split_devices_for_user_for_only_trusted_devices`]. /// /// A partitioning of the devices for a given user. #[derive(Default)] @@ -398,6 +400,60 @@ fn split_devices_for_user( device_owner_identity: &Option, only_allow_trusted_devices: bool, error_on_verified_user_problem: bool, +) -> DeviceBasedRecipientDevices { + if only_allow_trusted_devices { + let recipient_devices = split_devices_for_user_for_only_trusted_devices( + user_devices, + own_identity, + device_owner_identity, + ); + DeviceBasedRecipientDevices::Devices(recipient_devices) + } else if error_on_verified_user_problem { + split_devices_for_user_for_error_on_verified_user_problem_strategy( + user_devices, + own_identity, + device_owner_identity, + ) + } else { + let recipient_devices = split_devices_for_user_for_all_devices_strategy(user_devices); + DeviceBasedRecipientDevices::Devices(recipient_devices) + } +} + +/// Partition the list of a user's devices according to whether they should +/// receive the key, for [`CollectStrategy::DeviceBasedStrategy`] with +/// neither `error_on_verified_user_problem` nor `only_allow_trusted_devices`. +fn split_devices_for_user_for_all_devices_strategy( + user_devices: HashMap, +) -> RecipientDevicesForUser { + let (left, right) = user_devices.into_values().partition_map(|d| { + if d.is_blacklisted() { + Either::Right((d, WithheldCode::Blacklisted)) + } else { + Either::Left(d) + } + }); + + RecipientDevicesForUser { allowed_devices: left, denied_devices_with_code: right } +} + +/// Partition the list of a user's devices according to whether they should +/// receive the key, for [`CollectStrategy::DeviceBasedStrategy`] with +/// `error_on_verified_user_problem` (but not `only_allow_trusted_devices`) +/// +/// This function returns one of two values: +/// +/// * A list of the devices that should cause the transmission to fail due to +/// being unsigned. In this case, we don't bother to return the rest of the +/// devices, because we assume transmission will fail. +/// +/// * Otherwise, returns a [`RecipientDevicesForUser`] which lists, separately, +/// the devices that should receive the room key, and those that should +/// receive a withheld code. +fn split_devices_for_user_for_error_on_verified_user_problem_strategy( + user_devices: HashMap, + own_identity: &Option, + device_owner_identity: &Option, ) -> DeviceBasedRecipientDevices { let mut recipient_devices = RecipientDevicesForUser::default(); @@ -411,16 +467,11 @@ fn split_devices_for_user( } else if d.local_trust_state() == LocalTrust::Ignored { // Ignore the trust state of that device and share recipient_devices.allowed_devices.push(d); - } else if only_allow_trusted_devices && !d.is_verified(own_identity, device_owner_identity) - { - recipient_devices.denied_devices_with_code.push((d, WithheldCode::Unverified)); - } else if error_on_verified_user_problem - && is_unsigned_device_of_verified_user( - own_identity.as_ref(), - device_owner_identity.as_ref(), - &d, - ) - { + } else if is_unsigned_device_of_verified_user( + own_identity.as_ref(), + device_owner_identity.as_ref(), + &d, + ) { unsigned_devices_of_verified_users .get_or_insert_with(Vec::default) .push(d.device_id().to_owned()) @@ -472,6 +523,28 @@ fn split_devices_for_user_for_identity_based_strategy( } } +/// Partition the list of a user's devices according to whether they should +/// receive the key, for [`CollectStrategy::DeviceBasedStrategy`] with +/// `only_allow trusted_devices`. +fn split_devices_for_user_for_only_trusted_devices( + user_devices: HashMap, + own_identity: &Option, + device_owner_identity: &Option, +) -> RecipientDevicesForUser { + let (left, right) = user_devices.into_values().partition_map(|d| { + match ( + d.local_trust_state(), + d.is_cross_signing_trusted(own_identity, device_owner_identity), + ) { + (LocalTrust::BlackListed, _) => Either::Right((d, WithheldCode::Blacklisted)), + (LocalTrust::Ignored | LocalTrust::Verified, _) => Either::Left(d), + (LocalTrust::Unset, false) => Either::Right((d, WithheldCode::Unverified)), + (LocalTrust::Unset, true) => Either::Left(d), + } + }); + RecipientDevicesForUser { allowed_devices: left, denied_devices_with_code: right } +} + fn is_unsigned_device_of_verified_user( own_identity: Option<&OwnUserIdentityData>, device_owner_identity: Option<&UserIdentityData>, From 7c57f2cee4864f0d1fcd638fbade2e21d97de606 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Sun, 26 Jan 2025 22:57:15 +0000 Subject: [PATCH 28/55] crypto: split out new device collection strategies Rather than a bunch of flags on `DeviceBasedStrategy`, separate the strategies properly. --- bindings/matrix-sdk-crypto-ffi/src/lib.rs | 13 +- crates/matrix-sdk-crypto/CHANGELOG.md | 5 + crates/matrix-sdk-crypto/src/error.rs | 9 +- .../src/machine/tests/mod.rs | 5 +- .../src/olm/group_sessions/outbound.rs | 10 +- .../src/session_manager/group_sessions/mod.rs | 10 +- .../group_sessions/share_strategy.rs | 345 ++++++++++-------- ...ests__serialize_device_based_strategy.snap | 2 +- 8 files changed, 209 insertions(+), 190 deletions(-) diff --git a/bindings/matrix-sdk-crypto-ffi/src/lib.rs b/bindings/matrix-sdk-crypto-ffi/src/lib.rs index e6c09013e..50e99cb50 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/lib.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/lib.rs @@ -680,15 +680,20 @@ pub struct EncryptionSettings { impl From for RustEncryptionSettings { fn from(v: EncryptionSettings) -> Self { + let sharing_strategy = if v.only_allow_trusted_devices { + CollectStrategy::OnlyTrustedDevices + } else if v.error_on_verified_user_problem { + CollectStrategy::ErrorOnVerifiedUserProblem + } else { + CollectStrategy::AllDevices + }; + RustEncryptionSettings { algorithm: v.algorithm.into(), rotation_period: Duration::from_secs(v.rotation_period), rotation_period_msgs: v.rotation_period_msgs, history_visibility: v.history_visibility.into(), - sharing_strategy: CollectStrategy::DeviceBasedStrategy { - only_allow_trusted_devices: v.only_allow_trusted_devices, - error_on_verified_user_problem: v.error_on_verified_user_problem, - }, + sharing_strategy, } } } diff --git a/crates/matrix-sdk-crypto/CHANGELOG.md b/crates/matrix-sdk-crypto/CHANGELOG.md index 567c1df64..b7c7d84e2 100644 --- a/crates/matrix-sdk-crypto/CHANGELOG.md +++ b/crates/matrix-sdk-crypto/CHANGELOG.md @@ -8,6 +8,11 @@ All notable changes to this project will be documented in this file. ### Features +- [**breaking**] `CollectStrategy::DeviceBasedStrategy` is now split into three + separate strategies (`AllDevices`, `ErrorOnVerifiedUserProblem`, + `OnlyTrustedDevices`), to make the behaviour clearer. + ([#4581](https://github.com/matrix-org/matrix-rust-sdk/pull/4581)) + - Accept stable identifier `sender_device_keys` for MSC4147 (Including device keys with Olm-encrypted events). ([#4420](https://github.com/matrix-org/matrix-rust-sdk/pull/4420)) diff --git a/crates/matrix-sdk-crypto/src/error.rs b/crates/matrix-sdk-crypto/src/error.rs index 7a8ba63a8..6543d20d3 100644 --- a/crates/matrix-sdk-crypto/src/error.rs +++ b/crates/matrix-sdk-crypto/src/error.rs @@ -374,9 +374,7 @@ pub enum SetRoomSettingsError { pub enum SessionRecipientCollectionError { /// One or more verified users has one or more unsigned devices. /// - /// Happens only with [`CollectStrategy::DeviceBasedStrategy`] when - /// [`error_on_verified_user_problem`](`CollectStrategy::DeviceBasedStrategy::error_on_verified_user_problem`) - /// is true. + /// Happens only with [`CollectStrategy::ErrorOnVerifiedUserProblem`]. /// /// In order to resolve this, the caller can set the trust level of the /// affected devices to [`LocalTrust::Ignored`] or @@ -388,9 +386,8 @@ pub enum SessionRecipientCollectionError { /// One or more users was previously verified, but they have changed their /// identity. /// - /// Happens only with [`CollectStrategy::DeviceBasedStrategy`] when - /// [`error_on_verified_user_problem`](`CollectStrategy::DeviceBasedStrategy::error_on_verified_user_problem`) - /// is true, or with [`CollectStrategy::IdentityBasedStrategy`]. + /// Happens only with [`CollectStrategy::ErrorOnVerifiedUserProblem`] or + /// [`CollectStrategy::IdentityBasedStrategy`]. /// /// In order to resolve this, the user can: /// diff --git a/crates/matrix-sdk-crypto/src/machine/tests/mod.rs b/crates/matrix-sdk-crypto/src/machine/tests/mod.rs index 09ea51072..872650118 100644 --- a/crates/matrix-sdk-crypto/src/machine/tests/mod.rs +++ b/crates/matrix-sdk-crypto/src/machine/tests/mod.rs @@ -594,10 +594,7 @@ async fn test_withheld_unverified() { let encryption_settings = EncryptionSettings::default(); let encryption_settings = EncryptionSettings { - sharing_strategy: CollectStrategy::DeviceBasedStrategy { - only_allow_trusted_devices: true, - error_on_verified_user_problem: false, - }, + sharing_strategy: CollectStrategy::OnlyTrustedDevices, ..encryption_settings }; diff --git a/crates/matrix-sdk-crypto/src/olm/group_sessions/outbound.rs b/crates/matrix-sdk-crypto/src/olm/group_sessions/outbound.rs index 95a5949de..9e079eb79 100644 --- a/crates/matrix-sdk-crypto/src/olm/group_sessions/outbound.rs +++ b/crates/matrix-sdk-crypto/src/olm/group_sessions/outbound.rs @@ -788,10 +788,7 @@ mod tests { let settings = EncryptionSettings::new( content.clone(), HistoryVisibility::Joined, - CollectStrategy::DeviceBasedStrategy { - only_allow_trusted_devices: false, - error_on_verified_user_problem: false, - }, + CollectStrategy::AllDevices, ); assert_eq!(settings.rotation_period, ROTATION_PERIOD); @@ -803,10 +800,7 @@ mod tests { let settings = EncryptionSettings::new( content, HistoryVisibility::Shared, - CollectStrategy::DeviceBasedStrategy { - only_allow_trusted_devices: false, - error_on_verified_user_problem: false, - }, + CollectStrategy::AllDevices, ); assert_eq!(settings.rotation_period, Duration::from_millis(3600)); diff --git a/crates/matrix-sdk-crypto/src/session_manager/group_sessions/mod.rs b/crates/matrix-sdk-crypto/src/session_manager/group_sessions/mod.rs index 2e148471a..8c192e2dd 100644 --- a/crates/matrix-sdk-crypto/src/session_manager/group_sessions/mod.rs +++ b/crates/matrix-sdk-crypto/src/session_manager/group_sessions/mod.rs @@ -1163,10 +1163,7 @@ mod tests { .any(|d| d.user_id() == user_id && d.device_id() == device_id)); let settings = EncryptionSettings { - sharing_strategy: CollectStrategy::DeviceBasedStrategy { - only_allow_trusted_devices: true, - error_on_verified_user_problem: false, - }, + sharing_strategy: CollectStrategy::OnlyTrustedDevices, ..Default::default() }; let users = [user_id].into_iter(); @@ -1226,10 +1223,7 @@ mod tests { let users = keys_claim.one_time_keys.keys().map(Deref::deref); let settings = EncryptionSettings { - sharing_strategy: CollectStrategy::DeviceBasedStrategy { - only_allow_trusted_devices: true, - error_on_verified_user_problem: false, - }, + sharing_strategy: CollectStrategy::OnlyTrustedDevices, ..Default::default() }; diff --git a/crates/matrix-sdk-crypto/src/session_manager/group_sessions/share_strategy.rs b/crates/matrix-sdk-crypto/src/session_manager/group_sessions/share_strategy.rs index 4b952d120..10d4ed583 100644 --- a/crates/matrix-sdk-crypto/src/session_manager/group_sessions/share_strategy.rs +++ b/crates/matrix-sdk-crypto/src/session_manager/group_sessions/share_strategy.rs @@ -35,40 +35,43 @@ use crate::{Device, UserIdentity}; /// Strategy to collect the devices that should receive room keys for the /// current discussion. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))] +#[serde(from = "CollectStrategyDeserializationHelper")] pub enum CollectStrategy { - /// Device based sharing strategy. - DeviceBasedStrategy { - /// If `true`, devices that are not trusted will be excluded from the - /// conversation. A device is trusted if any of the following is true: - /// - It was manually marked as trusted. - /// - It was marked as verified via interactive verification. - /// - It is signed by its owner identity, and this identity has been - /// trusted via interactive verification. - /// - It is the current own device of the user. - only_allow_trusted_devices: bool, + /// Share with all (unblacklisted) devices. + #[default] + AllDevices, - /// If `true`, and a verified user has an unsigned device, key sharing - /// will fail with a - /// [`SessionRecipientCollectionError::VerifiedUserHasUnsignedDevice`]. - /// - /// If `true`, and a verified user has replaced their identity, key - /// sharing will fail with a - /// [`SessionRecipientCollectionError::VerifiedUserChangedIdentity`]. - /// - /// Otherwise, keys are shared with unsigned devices as normal. - /// - /// Once the problematic devices are blacklisted or whitelisted the - /// caller can retry to share a second time. - #[serde(default)] - error_on_verified_user_problem: bool, - }, + /// Share with all devices, except errors for *verified* users cause sharing + /// to fail with an error. + /// + /// In this strategy, if a verified user has an unsigned device, + /// key sharing will fail with a + /// [`SessionRecipientCollectionError::VerifiedUserHasUnsignedDevice`]. + /// If a verified user has replaced their identity, key + /// sharing will fail with a + /// [`SessionRecipientCollectionError::VerifiedUserChangedIdentity`]. + /// + /// Otherwise, keys are shared with unsigned devices as normal. + /// + /// Once the problematic devices are blacklisted or whitelisted the + /// caller can retry to share a second time. + ErrorOnVerifiedUserProblem, /// Share based on identity. Only distribute to devices signed by their /// owner. If a user has no published identity he will not receive /// any room keys. IdentityBasedStrategy, + + /// Only share keys with devices that we "trust". A device is trusted if any + /// of the following is true: + /// - It was manually marked as trusted. + /// - It was marked as verified via interactive verification. + /// - It is signed by its owner identity, and this identity has been + /// trusted via interactive verification. + /// - It is the current own device of the user. + OnlyTrustedDevices, } impl CollectStrategy { @@ -78,11 +81,47 @@ impl CollectStrategy { } } -impl Default for CollectStrategy { - fn default() -> Self { - CollectStrategy::DeviceBasedStrategy { - only_allow_trusted_devices: false, - error_on_verified_user_problem: false, +/// Deserialization helper for [`CollectStrategy`]. +#[derive(Deserialize)] +enum CollectStrategyDeserializationHelper { + /// `AllDevices`, `ErrorOnVerifiedUserProblem` and `OnlyTrustedDevices` used + /// to be implemented as a single strategy with flags. + DeviceBasedStrategy { + #[serde(default)] + error_on_verified_user_problem: bool, + + #[serde(default)] + only_allow_trusted_devices: bool, + }, + + AllDevices, + ErrorOnVerifiedUserProblem, + IdentityBasedStrategy, + OnlyTrustedDevices, +} + +impl From for CollectStrategy { + fn from(value: CollectStrategyDeserializationHelper) -> Self { + use CollectStrategyDeserializationHelper::*; + + match value { + DeviceBasedStrategy { + only_allow_trusted_devices: true, + error_on_verified_user_problem: _, + } => CollectStrategy::OnlyTrustedDevices, + DeviceBasedStrategy { + only_allow_trusted_devices: false, + error_on_verified_user_problem: true, + } => CollectStrategy::ErrorOnVerifiedUserProblem, + DeviceBasedStrategy { + only_allow_trusted_devices: false, + error_on_verified_user_problem: false, + } => CollectStrategy::AllDevices, + + AllDevices => CollectStrategy::AllDevices, + ErrorOnVerifiedUserProblem => CollectStrategy::ErrorOnVerifiedUserProblem, + IdentityBasedStrategy => CollectStrategy::IdentityBasedStrategy, + OnlyTrustedDevices => CollectStrategy::OnlyTrustedDevices, } } } @@ -149,51 +188,56 @@ pub(crate) async fn collect_session_recipients( // Get the recipient and withheld devices, based on the collection strategy. match settings.sharing_strategy { - CollectStrategy::DeviceBasedStrategy { - only_allow_trusted_devices, - error_on_verified_user_problem, - } => { + CollectStrategy::AllDevices => { + for user_id in users { + trace!( + "CollectStrategy::AllDevices: Considering recipient devices for user {}", + user_id + ); + let user_devices = store.get_device_data_for_user_filtered(user_id).await?; + + let recipient_devices = + split_devices_for_user_for_all_devices_strategy(user_devices); + update_recipients_for_user(&mut result, outbound, user_id, recipient_devices); + } + } + CollectStrategy::ErrorOnVerifiedUserProblem => { let mut unsigned_devices_of_verified_users: BTreeMap> = Default::default(); for user_id in users { - trace!("Considering recipient devices for user {}", user_id); + trace!("CollectStrategy::ErrorOnVerifiedUserProblem: Considering recipient devices for user {}", user_id); let user_devices = store.get_device_data_for_user_filtered(user_id).await?; - // We only need the user identity if `only_allow_trusted_devices` or - // `error_on_verified_user_problem` is set. - let device_owner_identity = - if only_allow_trusted_devices || error_on_verified_user_problem { - store.get_user_identity(user_id).await? - } else { - None - }; + let device_owner_identity = store.get_user_identity(user_id).await?; - if error_on_verified_user_problem - && has_identity_verification_violation( - own_identity.as_ref(), - device_owner_identity.as_ref(), - ) - { + if has_identity_verification_violation( + own_identity.as_ref(), + device_owner_identity.as_ref(), + ) { verified_users_with_new_identities.push(user_id.to_owned()); // No point considering the individual devices of this user. continue; } - let recipient_devices = split_devices_for_user( - user_devices, - &own_identity, - &device_owner_identity, - only_allow_trusted_devices, - error_on_verified_user_problem, - ); + let recipient_devices = + split_devices_for_user_for_error_on_verified_user_problem_strategy( + user_devices, + &own_identity, + &device_owner_identity, + ); match recipient_devices { - DeviceBasedRecipientDevices::UnsignedDevicesOfVerifiedUser(devices) => { + ErrorOnVerifiedUserProblemResult::UnsignedDevicesOfVerifiedUser(devices) => { unsigned_devices_of_verified_users.insert(user_id.to_owned(), devices); } - DeviceBasedRecipientDevices::Devices(recipient_devices) => { - result.update_for_user(outbound, user_id, recipient_devices); + ErrorOnVerifiedUserProblemResult::Devices(recipient_devices) => { + update_recipients_for_user( + &mut result, + outbound, + user_id, + recipient_devices, + ); } } } @@ -227,7 +271,7 @@ pub(crate) async fn collect_session_recipients( } for user_id in users { - trace!("Considering recipient devices for user {}", user_id); + trace!("CollectStrategy::IdentityBasedStrategy: Considering recipient devices for user {}", user_id); let user_devices = store.get_device_data_for_user_filtered(user_id).await?; let device_owner_identity = store.get_user_identity(user_id).await?; @@ -249,6 +293,22 @@ pub(crate) async fn collect_session_recipients( update_recipients_for_user(&mut result, outbound, user_id, recipient_devices); } } + + CollectStrategy::OnlyTrustedDevices => { + for user_id in users { + trace!("CollectStrategy::OnlyTrustedDevices: Considering recipient devices for user {}", user_id); + let user_devices = store.get_device_data_for_user_filtered(user_id).await?; + let device_owner_identity = store.get_user_identity(user_id).await?; + + let recipient_devices = split_devices_for_user_for_only_trusted_devices( + user_devices, + &own_identity, + &device_owner_identity, + ); + + update_recipients_for_user(&mut result, outbound, user_id, recipient_devices); + } + } } // We may have encountered previously-verified users who have changed their @@ -367,8 +427,9 @@ struct RecipientDevicesForUser { denied_devices_with_code: Vec<(DeviceData, WithheldCode)>, } -/// Result type for [`split_devices_for_user`]. -enum DeviceBasedRecipientDevices { +/// Result type for +/// [`split_devices_for_user_for_error_on_verified_user_problem_strategy`]. +enum ErrorOnVerifiedUserProblemResult { /// We found devices that should cause the transmission to fail, due to /// being an unsigned device belonging to a verified user. Only /// populated when `error_on_verified_user_problem` is set. @@ -379,50 +440,7 @@ enum DeviceBasedRecipientDevices { } /// Partition the list of a user's devices according to whether they should -/// receive the key, for [`CollectStrategy::DeviceBasedStrategy`]. -/// -/// This function returns one of two values: -/// -/// * If `error_on_verified_user_problem` is set, returns a list the devices -/// that should cause the transmission to fail due to being unsigned. In this -/// case, we don't bother to return the rest of the devices, because we assume -/// transmission will fail. -/// -/// (If error_on_verified_user_problem` is unset, these devices are otherwise -/// partitioned into `allowed_devices`.) -/// -/// * Otherwise, returns a [`RecipientDevicesForUser`] which lists, separately, -/// the devices that should receive the room key, and those that should -/// receive a withheld code. -fn split_devices_for_user( - user_devices: HashMap, - own_identity: &Option, - device_owner_identity: &Option, - only_allow_trusted_devices: bool, - error_on_verified_user_problem: bool, -) -> DeviceBasedRecipientDevices { - if only_allow_trusted_devices { - let recipient_devices = split_devices_for_user_for_only_trusted_devices( - user_devices, - own_identity, - device_owner_identity, - ); - DeviceBasedRecipientDevices::Devices(recipient_devices) - } else if error_on_verified_user_problem { - split_devices_for_user_for_error_on_verified_user_problem_strategy( - user_devices, - own_identity, - device_owner_identity, - ) - } else { - let recipient_devices = split_devices_for_user_for_all_devices_strategy(user_devices); - DeviceBasedRecipientDevices::Devices(recipient_devices) - } -} - -/// Partition the list of a user's devices according to whether they should -/// receive the key, for [`CollectStrategy::DeviceBasedStrategy`] with -/// neither `error_on_verified_user_problem` nor `only_allow_trusted_devices`. +/// receive the key, for [`CollectStrategy::AllDevices`]. fn split_devices_for_user_for_all_devices_strategy( user_devices: HashMap, ) -> RecipientDevicesForUser { @@ -438,8 +456,7 @@ fn split_devices_for_user_for_all_devices_strategy( } /// Partition the list of a user's devices according to whether they should -/// receive the key, for [`CollectStrategy::DeviceBasedStrategy`] with -/// `error_on_verified_user_problem` (but not `only_allow_trusted_devices`) +/// receive the key, for [`CollectStrategy::ErrorOnVerifiedUserProblem`]. /// /// This function returns one of two values: /// @@ -454,7 +471,7 @@ fn split_devices_for_user_for_error_on_verified_user_problem_strategy( user_devices: HashMap, own_identity: &Option, device_owner_identity: &Option, -) -> DeviceBasedRecipientDevices { +) -> ErrorOnVerifiedUserProblemResult { let mut recipient_devices = RecipientDevicesForUser::default(); // We construct unsigned_devices_of_verified_users lazily, because chances are @@ -524,8 +541,7 @@ fn split_devices_for_user_for_identity_based_strategy( } /// Partition the list of a user's devices according to whether they should -/// receive the key, for [`CollectStrategy::DeviceBasedStrategy`] with -/// `only_allow trusted_devices`. +/// receive the key, for [`CollectStrategy::OnlyTrustedDevices`]. fn split_devices_for_user_for_only_trusted_devices( user_devices: HashMap, own_identity: &Option, @@ -653,20 +669,65 @@ mod tests { machine } - /// Assert that [`CollectStrategy::DeviceBasedStrategy`] retains the same + /// Assert that [`CollectStrategy::AllDevices`] retains the same /// serialization format. #[test] fn test_serialize_device_based_strategy() { - let encryption_settings = device_based_strategy_settings(); + let encryption_settings = all_devices_strategy_settings(); let serialized = serde_json::to_string(&encryption_settings).unwrap(); assert_snapshot!(serialized); } + /// [`CollectStrategy::AllDevices`] used to be known as + /// `DeviceBasedStrategy`. Check we can still deserialize the old + /// representation. + #[test] + fn test_deserialize_old_device_based_strategy() { + let settings: EncryptionSettings = serde_json::from_value(json!({ + "algorithm": "m.megolm.v1.aes-sha2", + "rotation_period":{"secs":604800,"nanos":0}, + "rotation_period_msgs":100, + "history_visibility":"shared", + "sharing_strategy":{"DeviceBasedStrategy":{"only_allow_trusted_devices":false,"error_on_verified_user_problem":false}}, + })).unwrap(); + assert_matches!(settings.sharing_strategy, CollectStrategy::AllDevices); + } + + /// [`CollectStrategy::ErrorOnVerifiedUserProblem`] used to be represented + /// as a variant on the former `DeviceBasedStrategy`. Check we can still + /// deserialize the old representation. + #[test] + fn test_deserialize_old_error_on_verified_user_problem() { + let settings: EncryptionSettings = serde_json::from_value(json!({ + "algorithm": "m.megolm.v1.aes-sha2", + "rotation_period":{"secs":604800,"nanos":0}, + "rotation_period_msgs":100, + "history_visibility":"shared", + "sharing_strategy":{"DeviceBasedStrategy":{"only_allow_trusted_devices":false,"error_on_verified_user_problem":true}}, + })).unwrap(); + assert_matches!(settings.sharing_strategy, CollectStrategy::ErrorOnVerifiedUserProblem); + } + + /// [`CollectStrategy::OnlyTrustedDevices`] used to be represented as a + /// variant on the former `DeviceBasedStrategy`. Check we can still + /// deserialize the old representation. + #[test] + fn test_deserialize_old_only_trusted_devices_strategy() { + let settings: EncryptionSettings = serde_json::from_value(json!({ + "algorithm": "m.megolm.v1.aes-sha2", + "rotation_period":{"secs":604800,"nanos":0}, + "rotation_period_msgs":100, + "history_visibility":"shared", + "sharing_strategy":{"DeviceBasedStrategy":{"only_allow_trusted_devices":true,"error_on_verified_user_problem":false}}, + })).unwrap(); + assert_matches!(settings.sharing_strategy, CollectStrategy::OnlyTrustedDevices); + } + #[async_test] async fn test_share_with_per_device_strategy_to_all() { let machine = set_up_test_machine().await; - let encryption_settings = device_based_strategy_settings(); + let encryption_settings = all_devices_strategy_settings(); let group_session = create_test_outbound_group_session(&machine, &encryption_settings); @@ -700,33 +761,11 @@ mod tests { } #[async_test] - async fn test_share_with_per_device_strategy_only_trusted() { - test_share_only_trusted_helper(false).await; - } - - /// Variation of [`test_share_with_per_device_strategy_only_trusted`] to - /// test the interaction between - /// [`only_allow_trusted_devices`](`CollectStrategy::DeviceBasedStrategy::only_allow_trusted_devices`) and - /// [`error_on_verified_user_problem`](`CollectStrategy::DeviceBasedStrategy::error_on_verified_user_problem`). - /// - /// (Given that untrusted devices are ignored, we do not expect - /// [`collect_session_recipients`] to return an error, despite the presence - /// of unsigned devices.) - #[async_test] - async fn test_share_with_per_device_strategy_only_trusted_error_on_unsigned_of_verified() { - test_share_only_trusted_helper(true).await; - } - - /// Common helper for [`test_share_with_per_device_strategy_only_trusted`] - /// and [`test_share_with_per_device_strategy_only_trusted_error_on_unsigned_of_verified`]. - async fn test_share_only_trusted_helper(error_on_verified_user_problem: bool) { + async fn test_share_with_only_trusted_strategy() { let machine = set_up_test_machine().await; let encryption_settings = EncryptionSettings { - sharing_strategy: CollectStrategy::DeviceBasedStrategy { - only_allow_trusted_devices: true, - error_on_verified_user_problem, - }, + sharing_strategy: CollectStrategy::OnlyTrustedDevices, ..Default::default() }; @@ -1458,10 +1497,7 @@ mod tests { async fn test_should_rotate_based_on_visibility() { let machine = set_up_test_machine().await; - let strategy = CollectStrategy::DeviceBasedStrategy { - only_allow_trusted_devices: false, - error_on_verified_user_problem: false, - }; + let strategy = CollectStrategy::AllDevices; let encryption_settings = EncryptionSettings { sharing_strategy: strategy.clone(), @@ -1507,7 +1543,7 @@ mod tests { let machine = set_up_test_machine().await; let fake_room_id = room_id!("!roomid:localhost"); - let encryption_settings = device_based_strategy_settings(); + let encryption_settings = all_devices_strategy_settings(); let requests = machine .share_room_key( @@ -1599,25 +1635,16 @@ mod tests { machine } - /// [`EncryptionSettings`] with [`CollectStrategy::DeviceBasedStrategy`] - /// (with default settings) - fn device_based_strategy_settings() -> EncryptionSettings { - EncryptionSettings { - sharing_strategy: CollectStrategy::DeviceBasedStrategy { - only_allow_trusted_devices: false, - error_on_verified_user_problem: false, - }, - ..Default::default() - } + /// [`EncryptionSettings`] with [`CollectStrategy::AllDevices`] + fn all_devices_strategy_settings() -> EncryptionSettings { + EncryptionSettings { sharing_strategy: CollectStrategy::AllDevices, ..Default::default() } } - /// [`EncryptionSettings`] with `error_on_verified_user_problem` set + /// [`EncryptionSettings`] with + /// [`CollectStrategy::ErrorOnVerifiedUserProblem`] fn error_on_verification_problem_encryption_settings() -> EncryptionSettings { EncryptionSettings { - sharing_strategy: CollectStrategy::DeviceBasedStrategy { - only_allow_trusted_devices: false, - error_on_verified_user_problem: true, - }, + sharing_strategy: CollectStrategy::ErrorOnVerifiedUserProblem, ..Default::default() } } diff --git a/crates/matrix-sdk-crypto/src/session_manager/group_sessions/snapshots/matrix_sdk_crypto__session_manager__group_sessions__share_strategy__tests__serialize_device_based_strategy.snap b/crates/matrix-sdk-crypto/src/session_manager/group_sessions/snapshots/matrix_sdk_crypto__session_manager__group_sessions__share_strategy__tests__serialize_device_based_strategy.snap index 0b1ffeaf8..add8ae3d0 100644 --- a/crates/matrix-sdk-crypto/src/session_manager/group_sessions/snapshots/matrix_sdk_crypto__session_manager__group_sessions__share_strategy__tests__serialize_device_based_strategy.snap +++ b/crates/matrix-sdk-crypto/src/session_manager/group_sessions/snapshots/matrix_sdk_crypto__session_manager__group_sessions__share_strategy__tests__serialize_device_based_strategy.snap @@ -2,4 +2,4 @@ source: crates/matrix-sdk-crypto/src/session_manager/group_sessions/share_strategy.rs expression: serialized --- -{"algorithm":"m.megolm.v1.aes-sha2","rotation_period":{"secs":604800,"nanos":0},"rotation_period_msgs":100,"history_visibility":"shared","sharing_strategy":{"DeviceBasedStrategy":{"only_allow_trusted_devices":false,"error_on_verified_user_problem":false}}} +{"algorithm":"m.megolm.v1.aes-sha2","rotation_period":{"secs":604800,"nanos":0},"rotation_period_msgs":100,"history_visibility":"shared","sharing_strategy":"AllDevices"} From f43edbd31f4b4d99c55ff1d530a453f7880a7cca Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Sun, 26 Jan 2025 22:21:56 +0000 Subject: [PATCH 29/55] refactor(crypto): split up `split_devices_for_user_for_error_on_verified_user_problem_strategy` to make it easier to grok, I hope --- .../group_sessions/share_strategy.rs | 54 ++++++++++++++----- 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/session_manager/group_sessions/share_strategy.rs b/crates/matrix-sdk-crypto/src/session_manager/group_sessions/share_strategy.rs index 10d4ed583..5c8f48ec5 100644 --- a/crates/matrix-sdk-crypto/src/session_manager/group_sessions/share_strategy.rs +++ b/crates/matrix-sdk-crypto/src/session_manager/group_sessions/share_strategy.rs @@ -479,28 +479,54 @@ fn split_devices_for_user_for_error_on_verified_user_problem_strategy( let mut unsigned_devices_of_verified_users: Option> = None; for d in user_devices.into_values() { - if d.is_blacklisted() { - recipient_devices.denied_devices_with_code.push((d, WithheldCode::Blacklisted)); - } else if d.local_trust_state() == LocalTrust::Ignored { - // Ignore the trust state of that device and share - recipient_devices.allowed_devices.push(d); - } else if is_unsigned_device_of_verified_user( + match handle_device_for_user_for_error_on_verified_user_problem_strategy( + &d, own_identity.as_ref(), device_owner_identity.as_ref(), - &d, ) { - unsigned_devices_of_verified_users - .get_or_insert_with(Vec::default) - .push(d.device_id().to_owned()) - } else { - recipient_devices.allowed_devices.push(d); + ErrorOnVerifiedUserProblemDeviceDecision::Ok => { + recipient_devices.allowed_devices.push(d) + } + ErrorOnVerifiedUserProblemDeviceDecision::Withhold(code) => { + recipient_devices.denied_devices_with_code.push((d, code)) + } + ErrorOnVerifiedUserProblemDeviceDecision::UnsignedOfVerified => { + unsigned_devices_of_verified_users + .get_or_insert_with(Vec::default) + .push(d.device_id().to_owned()) + } } } if let Some(devices) = unsigned_devices_of_verified_users { - DeviceBasedRecipientDevices::UnsignedDevicesOfVerifiedUser(devices) + ErrorOnVerifiedUserProblemResult::UnsignedDevicesOfVerifiedUser(devices) } else { - DeviceBasedRecipientDevices::Devices(recipient_devices) + ErrorOnVerifiedUserProblemResult::Devices(recipient_devices) + } +} + +/// Result type for +/// [`handle_device_for_user_for_error_on_verified_user_problem_strategy`]. +enum ErrorOnVerifiedUserProblemDeviceDecision { + Ok, + Withhold(WithheldCode), + UnsignedOfVerified, +} + +fn handle_device_for_user_for_error_on_verified_user_problem_strategy( + device: &DeviceData, + own_identity: Option<&OwnUserIdentityData>, + device_owner_identity: Option<&UserIdentityData>, +) -> ErrorOnVerifiedUserProblemDeviceDecision { + if device.is_blacklisted() { + ErrorOnVerifiedUserProblemDeviceDecision::Withhold(WithheldCode::Blacklisted) + } else if device.local_trust_state() == LocalTrust::Ignored { + // Ignore the trust state of that device and share + ErrorOnVerifiedUserProblemDeviceDecision::Ok + } else if is_unsigned_device_of_verified_user(own_identity, device_owner_identity, device) { + ErrorOnVerifiedUserProblemDeviceDecision::UnsignedOfVerified + } else { + ErrorOnVerifiedUserProblemDeviceDecision::Ok } } From 468a7ac883b03404dcb9ef761b77678d242eb579 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 27 Jan 2025 12:12:22 +0100 Subject: [PATCH 30/55] doc(ffi): Remove useless `#[cfg(doc)]` imports. This patch removes 2 useless imports that are behind a `#[cfg(doc)]` but never used. --- bindings/matrix-sdk-ffi/src/timeline/mod.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/bindings/matrix-sdk-ffi/src/timeline/mod.rs b/bindings/matrix-sdk-ffi/src/timeline/mod.rs index ab49adfdd..b4d47d45d 100644 --- a/bindings/matrix-sdk-ffi/src/timeline/mod.rs +++ b/bindings/matrix-sdk-ffi/src/timeline/mod.rs @@ -19,8 +19,6 @@ use as_variant::as_variant; use content::{InReplyToDetails, RepliedToEventDetails}; use eyeball_im::VectorDiff; use futures_util::{pin_mut, StreamExt as _}; -#[cfg(doc)] -use matrix_sdk::crypto::CollectStrategy; use matrix_sdk::{ attachment::{ AttachmentConfig, AttachmentInfo, BaseAudioInfo, BaseFileInfo, BaseImageInfo, @@ -63,8 +61,6 @@ use tracing::{error, warn}; use uuid::Uuid; use self::content::{Reaction, ReactionSenderData, TimelineItemContent}; -#[cfg(doc)] -use crate::client_builder::ClientBuilder; use crate::{ client::ProgressWatcher, error::{ClientError, RoomError}, From 254ac6f2ce696f03734c40e9e8737ddb609eb250 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 27 Jan 2025 12:13:38 +0100 Subject: [PATCH 31/55] refactor(ui): Unify the `Timeline` pagination API. This patch simplifies the `Timeline` pagination API as follows: - a unique `paginate_backwards` method (no more `focused_paginate_backwards` and `live_paginate_backwards`), - a unique `paginate_forwards` method (no more `focused_paginate_forwards`, the `live` variant was absent). The idea is to unify pagination by hiding the `live` and `focused` mode. It was already partially the case with `paginate_backards`, but the `live` and `focused` variants were also present. I believe it creates an unnecessary confusion. --- bindings/matrix-sdk-ffi/src/timeline/mod.rs | 8 +++--- .../matrix-sdk-ui/src/timeline/pagination.rs | 27 +++++++------------ .../tests/integration/timeline/edit.rs | 2 +- .../tests/integration/timeline/focus_event.rs | 4 +-- .../tests/integration/timeline/pagination.rs | 22 +++++++-------- labs/multiverse/src/main.rs | 2 +- 6 files changed, 29 insertions(+), 36 deletions(-) diff --git a/bindings/matrix-sdk-ffi/src/timeline/mod.rs b/bindings/matrix-sdk-ffi/src/timeline/mod.rs index b4d47d45d..1c3dbe3ae 100644 --- a/bindings/matrix-sdk-ffi/src/timeline/mod.rs +++ b/bindings/matrix-sdk-ffi/src/timeline/mod.rs @@ -267,16 +267,16 @@ impl Timeline { /// Paginate backwards, whether we are in focused mode or in live mode. /// - /// Returns whether we hit the end of the timeline or not. + /// Returns whether we hit the start of the timeline or not. pub async fn paginate_backwards(&self, num_events: u16) -> Result { Ok(self.inner.paginate_backwards(num_events).await?) } - /// Paginate forwards, when in focused mode. + /// Paginate forwards, whether we are in focused mode or in live mode. /// /// Returns whether we hit the end of the timeline or not. - pub async fn focused_paginate_forwards(&self, num_events: u16) -> Result { - Ok(self.inner.focused_paginate_forwards(num_events).await?) + pub async fn paginate_forwards(&self, num_events: u16) -> Result { + Ok(self.inner.paginate_forwards(num_events).await?) } pub async fn send_read_receipt( diff --git a/crates/matrix-sdk-ui/src/timeline/pagination.rs b/crates/matrix-sdk-ui/src/timeline/pagination.rs index e7322b215..4c79f5a4b 100644 --- a/crates/matrix-sdk-ui/src/timeline/pagination.rs +++ b/crates/matrix-sdk-ui/src/timeline/pagination.rs @@ -36,26 +36,20 @@ impl super::Timeline { if self.controller.is_live().await { Ok(self.live_paginate_backwards(num_events).await?) } else { - Ok(self.focused_paginate_backwards(num_events).await?) + Ok(self.controller.focused_paginate_backwards(num_events).await?) } } - /// Assuming the timeline is focused on an event, starts a forwards - /// pagination. + /// Add more events to the end of the timeline. /// /// Returns whether we hit the end of the timeline. - #[instrument(skip_all)] - pub async fn focused_paginate_forwards(&self, num_events: u16) -> Result { - Ok(self.controller.focused_paginate_forwards(num_events).await?) - } - - /// Assuming the timeline is focused on an event, starts a backwards - /// pagination. - /// - /// Returns whether we hit the start of the timeline. - #[instrument(skip(self), fields(room_id = ?self.room().room_id()))] - pub async fn focused_paginate_backwards(&self, num_events: u16) -> Result { - Ok(self.controller.focused_paginate_backwards(num_events).await?) + #[instrument(skip_all, fields(room_id = ?self.room().room_id()))] + pub async fn paginate_forwards(&self, num_events: u16) -> Result { + if self.controller.is_live().await { + Ok(true) + } else { + Ok(self.controller.focused_paginate_forwards(num_events).await?) + } } /// Paginate backwards in live mode. @@ -64,8 +58,7 @@ impl super::Timeline { /// on a specific event. /// /// Returns whether we hit the start of the timeline. - #[instrument(skip_all, fields(room_id = ?self.room().room_id()))] - pub async fn live_paginate_backwards(&self, batch_size: u16) -> event_cache::Result { + async fn live_paginate_backwards(&self, batch_size: u16) -> event_cache::Result { let pagination = self.event_cache.pagination(); let result = pagination diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/edit.rs b/crates/matrix-sdk-ui/tests/integration/timeline/edit.rs index a3eb68aab..228100894 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/edit.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/edit.rs @@ -907,7 +907,7 @@ impl PendingEditHelper { .mount() .await; - self.timeline.live_paginate_backwards(batch_size).await.unwrap(); + self.timeline.paginate_backwards(batch_size).await.unwrap(); } } diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/focus_event.rs b/crates/matrix-sdk-ui/tests/integration/timeline/focus_event.rs index 47569fa5c..0eb5e876e 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/focus_event.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/focus_event.rs @@ -121,7 +121,7 @@ async fn test_new_focused() { ) .await; - let hit_start = timeline.focused_paginate_backwards(20).await.unwrap(); + let hit_start = timeline.paginate_backwards(20).await.unwrap(); assert!(hit_start); server.reset().await; @@ -161,7 +161,7 @@ async fn test_new_focused() { ) .await; - let hit_start = timeline.focused_paginate_forwards(20).await.unwrap(); + let hit_start = timeline.paginate_forwards(20).await.unwrap(); assert!(!hit_start); // because we gave it another next2 token. server.reset().await; diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/pagination.rs b/crates/matrix-sdk-ui/tests/integration/timeline/pagination.rs index f8efef54b..c833bea73 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/pagination.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/pagination.rs @@ -76,7 +76,7 @@ async fn test_back_pagination() { .await; let paginate = async { - timeline.live_paginate_backwards(10).await.unwrap(); + timeline.paginate_backwards(10).await.unwrap(); server.reset().await; }; let observe_paginating = async { @@ -145,7 +145,7 @@ async fn test_back_pagination() { .mount(&server) .await; - let hit_start = timeline.live_paginate_backwards(10).await.unwrap(); + let hit_start = timeline.paginate_backwards(10).await.unwrap(); assert!(hit_start); assert_next_eq!( back_pagination_status, @@ -218,7 +218,7 @@ async fn test_back_pagination_highlighted() { .mount(&server) .await; - timeline.live_paginate_backwards(10).await.unwrap(); + timeline.paginate_backwards(10).await.unwrap(); server.reset().await; assert_let!(Some(timeline_updates) = timeline_stream.next().await); @@ -289,7 +289,7 @@ async fn test_wait_for_token() { mock_sync(&server, sync_builder.build_json_sync_response(), None).await; let paginate = async { - timeline.live_paginate_backwards(10).await.unwrap(); + timeline.paginate_backwards(10).await.unwrap(); }; let observe_paginating = async { assert_eq!(back_pagination_status.next().await, Some(LiveBackPaginationStatus::Paginating)); @@ -354,10 +354,10 @@ async fn test_dedup_pagination() { // If I try to paginate twice at the same time, let paginate_1 = async { - timeline.live_paginate_backwards(10).await.unwrap(); + timeline.paginate_backwards(10).await.unwrap(); }; let paginate_2 = async { - timeline.live_paginate_backwards(10).await.unwrap(); + timeline.paginate_backwards(10).await.unwrap(); }; timeout(Duration::from_secs(5), join(paginate_1, paginate_2)).await.unwrap(); @@ -443,7 +443,7 @@ async fn test_timeline_reset_while_paginating() { let (_, mut back_pagination_status) = timeline.live_back_pagination_status().await.unwrap(); - let paginate = async { timeline.live_paginate_backwards(10).await.unwrap() }; + let paginate = async { timeline.paginate_backwards(10).await.unwrap() }; let observe_paginating = async { let mut seen_paginating = false; @@ -608,7 +608,7 @@ async fn test_empty_chunk() { .await; let paginate = async { - timeline.live_paginate_backwards(10).await.unwrap(); + timeline.paginate_backwards(10).await.unwrap(); server.reset().await; }; let observe_paginating = async { @@ -719,7 +719,7 @@ async fn test_until_num_items_with_empty_chunk() { .await; let paginate = async { - timeline.live_paginate_backwards(10).await.unwrap(); + timeline.paginate_backwards(10).await.unwrap(); }; let observe_paginating = async { assert_eq!(back_pagination_status.next().await, Some(LiveBackPaginationStatus::Paginating)); @@ -771,7 +771,7 @@ async fn test_until_num_items_with_empty_chunk() { assert!(date_divider.is_date_divider()); } - timeline.live_paginate_backwards(10).await.unwrap(); + timeline.paginate_backwards(10).await.unwrap(); assert_let!(Some(timeline_updates) = timeline_stream.next().await); @@ -821,7 +821,7 @@ async fn test_back_pagination_aborted() { let paginate = spawn({ let timeline = timeline.clone(); async move { - timeline.live_paginate_backwards(10).await.unwrap(); + timeline.paginate_backwards(10).await.unwrap(); } }); diff --git a/labs/multiverse/src/main.rs b/labs/multiverse/src/main.rs index 061eaf208..873d1aae6 100644 --- a/labs/multiverse/src/main.rs +++ b/labs/multiverse/src/main.rs @@ -418,7 +418,7 @@ impl App { // Start a new one, request batches of 20 events, stop after 10 timeline items // have been added. *pagination = Some(spawn(async move { - if let Err(err) = sdk_timeline.live_paginate_backwards(20).await { + if let Err(err) = sdk_timeline.paginate_backwards(20).await { // TODO: would be nice to be able to set the status // message remotely? //self.set_status_message(format!( From 08201702617c986ae97239c1e94879f73bd98438 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 27 Jan 2025 14:55:21 +0100 Subject: [PATCH 32/55] doc(ui): Update the `CHANGELOG`. --- crates/matrix-sdk-ui/CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/matrix-sdk-ui/CHANGELOG.md b/crates/matrix-sdk-ui/CHANGELOG.md index 42e43a399..2b84ee3f6 100644 --- a/crates/matrix-sdk-ui/CHANGELOG.md +++ b/crates/matrix-sdk-ui/CHANGELOG.md @@ -24,6 +24,13 @@ All notable changes to this project will be documented in this file. implement `Into` also implement `Into`. ([#4451](https://github.com/matrix-org/matrix-rust-sdk/pull/4451)) +### Refactor + +- [**breaking**] `Timeline::paginate_forwards` and `Timeline::paginate_backwards` + are unified to work on a live or focused timeline. + `Timeline::live_paginate_*` and `Timeline::focused_paginate_*` have been + removed ([#4584](https://github.com/matrix-org/matrix-rust-sdk/pull/4584)). + ## [0.9.0] - 2024-12-18 ### Bug Fixes @@ -63,7 +70,6 @@ All notable changes to this project will be documented in this file. - `EncryptionSyncService` and `Notification` are using `Client::cross_process_store_locks_holder_name`. - ### Refactor - [**breaking**] `Timeline::edit` now takes a `RoomMessageEventContentWithoutRelation`. From 408b843156b0274017a2165482ad3f437aa1755c Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Mon, 27 Jan 2025 16:20:23 +0000 Subject: [PATCH 33/55] test: fix cross-signing in legacy dehydrated device test We missed a call to `sign_device_keys`. Pull out a test helper to make it more obvious where this code is coming from. --- .../src/dehydrated_devices.rs | 67 ++++++++++++++----- 1 file changed, 50 insertions(+), 17 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/dehydrated_devices.rs b/crates/matrix-sdk-crypto/src/dehydrated_devices.rs index 986450971..62ea9b08e 100644 --- a/crates/matrix-sdk-crypto/src/dehydrated_devices.rs +++ b/crates/matrix-sdk-crypto/src/dehydrated_devices.rs @@ -396,12 +396,20 @@ mod tests { use js_option::JsOption; use matrix_sdk_test::async_test; use ruma::{ - api::client::keys::get_keys::v3::Response as KeysQueryResponse, assign, - encryption::DeviceKeys, events::AnyToDeviceEvent, room_id, serde::Raw, user_id, DeviceId, - RoomId, TransactionId, UserId, + api::client::{ + dehydrated_device::put_dehydrated_device, + keys::get_keys::v3::Response as KeysQueryResponse, + }, + assign, + encryption::DeviceKeys, + events::AnyToDeviceEvent, + room_id, + serde::Raw, + user_id, DeviceId, RoomId, TransactionId, UserId, }; use crate::{ + dehydrated_devices::DehydratedDevice, machine::{ test_helpers::{create_session, get_prepared_machine_test_helper}, tests::to_device_requests_to_content, @@ -625,24 +633,18 @@ mod tests { let alice = get_olm_machine().await; let dehydrated_device = alice.dehydrated_devices().create().await.unwrap(); + let mut request = + legacy_dehydrated_device_keys_for_upload(&dehydrated_device, &pickle_key()).await; - let mut transaction = dehydrated_device.store.transaction().await; - let account = transaction.account().await.unwrap(); - account.generate_fallback_key_if_needed(); - - let (device_keys, mut one_time_keys, _fallback_keys) = account.keys_for_upload(); - let device_keys = device_keys.unwrap(); - - let device_data = account.legacy_dehydrate(&pickle_key().inner); - let device_id = account.device_id().to_owned(); - transaction.commit().await.unwrap(); - - let (key_id, one_time_key) = one_time_keys + let (key_id, one_time_key) = request + .one_time_keys .pop_first() .expect("The dehydrated device creation request should contain a one-time key"); + let device_id = request.device_id; + // Ensure that we know about the public keys of the dehydrated device. - receive_device_keys(&alice, user_id(), &device_id, device_keys.to_raw()).await; + receive_device_keys(&alice, user_id(), &device_id, request.device_keys).await; // Create a 1-to-1 Olm session with the dehydrated device. create_session(&alice, user_id(), &device_id, key_id, one_time_key).await; @@ -666,7 +668,7 @@ mod tests { // Rehydrate the device. let rehydrated = bob .dehydrated_devices() - .rehydrate(&pickle_key(), &device_id, device_data) + .rehydrate(&pickle_key(), &device_id, request.device_data) .await .expect("We should be able to rehydrate the device"); @@ -696,4 +698,35 @@ mod tests { "The session ids of the imported room key and the outbound group session should match" ); } + + /// Duplicates the behaviour of [`DehydratedDevice::keys_for_upload`], + /// except that it calls [`Account::legacy_dehydrate`] instead of + /// [`Account::dehydrate`]. + async fn legacy_dehydrated_device_keys_for_upload( + dehydrated_device: &DehydratedDevice, + pickle_key: &DehydratedDeviceKey, + ) -> put_dehydrated_device::unstable::Request { + let mut transaction = dehydrated_device.store.transaction().await; + let account = transaction.account().await.unwrap(); + account.generate_fallback_key_if_needed(); + + let (device_keys, one_time_keys, fallback_keys) = account.keys_for_upload(); + let mut device_keys = device_keys.unwrap(); + dehydrated_device + .store + .private_identity() + .lock() + .await + .sign_device_keys(&mut device_keys) + .await + .expect("Should be able to cross-sign a device"); + + let device_id = account.device_id().to_owned(); + let device_data = account.legacy_dehydrate(pickle_key.inner.as_ref()); + transaction.commit().await.unwrap(); + + assign!(put_dehydrated_device::unstable::Request::new(device_id, device_data, device_keys.to_raw()), { + one_time_keys, fallback_keys + }) + } } From 6dc5b33d87aa0e4440c1371935f5f4070efe53e5 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 27 Jan 2025 17:09:34 +0100 Subject: [PATCH 34/55] chore(ui): Remove useless `trace!`. This patch removes useless `trace!` calls. --- crates/matrix-sdk-ui/src/timeline/controller/mod.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/matrix-sdk-ui/src/timeline/controller/mod.rs b/crates/matrix-sdk-ui/src/timeline/controller/mod.rs index e5f963d15..c370f830d 100644 --- a/crates/matrix-sdk-ui/src/timeline/controller/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/controller/mod.rs @@ -484,7 +484,6 @@ impl TimelineController

{ Vector>, impl Stream>> + SendOutsideWasm, ) { - trace!("Creating timeline items signal"); let state = self.state.read().await; (state.items.clone_items(), state.items.subscribe().into_stream()) } @@ -492,7 +491,6 @@ impl TimelineController

{ pub(super) async fn subscribe_batched( &self, ) -> (Vector>, impl Stream>>>) { - trace!("Creating timeline items signal"); let state = self.state.read().await; (state.items.clone_items(), state.items.subscribe().into_batched_stream()) } @@ -505,7 +503,6 @@ impl TimelineController

{ U: Clone, F: Fn(Arc) -> Option, { - trace!("Creating timeline items signal"); self.state.read().await.items.subscribe().filter_map(f) } From 5b3b87d3e241e6f21316f5971bfc4d2d66a136aa Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 27 Jan 2025 17:14:05 +0100 Subject: [PATCH 35/55] chore(ui): Rename `Timeline::subscribe_batched` to `::subscribe`. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch renames `Timeline::subscribe_batched` to `Timeline::subscribe`. Since the `Timeline::subscribe` method has been removed because unused, it no longer makes sense to have a “batched” variant here. Let's simplify things! --- benchmarks/benches/room_bench.rs | 2 +- bindings/matrix-sdk-ffi/src/timeline/mod.rs | 2 +- crates/matrix-sdk-ui/src/timeline/mod.rs | 10 +++++---- .../tests/integration/room_list_service.rs | 4 ++-- .../tests/integration/timeline/echo.rs | 4 ++-- .../tests/integration/timeline/edit.rs | 16 +++++++------- .../tests/integration/timeline/focus_event.rs | 8 +++---- .../tests/integration/timeline/mod.rs | 16 +++++++------- .../tests/integration/timeline/pagination.rs | 8 +++---- .../integration/timeline/pinned_event.rs | 22 +++++++++---------- .../tests/integration/timeline/queue.rs | 4 ++-- .../tests/integration/timeline/reactions.rs | 6 ++--- .../integration/timeline/read_receipts.rs | 6 ++--- .../tests/integration/timeline/replies.rs | 2 +- .../integration/timeline/sliding_sync.rs | 2 +- .../tests/integration/timeline/subscribe.rs | 8 +++---- examples/timeline/src/main.rs | 2 +- labs/multiverse/src/main.rs | 2 +- .../src/tests/sliding_sync/room.rs | 2 +- .../src/tests/timeline.rs | 4 ++-- 20 files changed, 66 insertions(+), 64 deletions(-) diff --git a/benchmarks/benches/room_bench.rs b/benchmarks/benches/room_bench.rs index 577bbd762..e8ac47ab0 100644 --- a/benchmarks/benches/room_bench.rs +++ b/benchmarks/benches/room_bench.rs @@ -198,7 +198,7 @@ pub fn load_pinned_events_benchmark(c: &mut Criterion) { .await .expect("Could not create timeline"); - let (items, _) = timeline.subscribe_batched().await; + let (items, _) = timeline.subscribe().await; assert_eq!(items.len(), PINNED_EVENTS_COUNT + 1); timeline.clear().await; }); diff --git a/bindings/matrix-sdk-ffi/src/timeline/mod.rs b/bindings/matrix-sdk-ffi/src/timeline/mod.rs index 1c3dbe3ae..5f7a0484b 100644 --- a/bindings/matrix-sdk-ffi/src/timeline/mod.rs +++ b/bindings/matrix-sdk-ffi/src/timeline/mod.rs @@ -212,7 +212,7 @@ pub struct UploadParameters { #[matrix_sdk_ffi_macros::export] impl Timeline { pub async fn add_listener(&self, listener: Box) -> Arc { - let (timeline_items, timeline_stream) = self.inner.subscribe_batched().await; + let (timeline_items, timeline_stream) = self.inner.subscribe().await; Arc::new(TaskHandle::new(RUNTIME.spawn(async move { pin_mut!(timeline_stream); diff --git a/crates/matrix-sdk-ui/src/timeline/mod.rs b/crates/matrix-sdk-ui/src/timeline/mod.rs index 21d7101f4..3b419f9e4 100644 --- a/crates/matrix-sdk-ui/src/timeline/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/mod.rs @@ -266,11 +266,13 @@ impl Timeline { } } - /// Get the current timeline items, and a batched stream of changes. + /// Get the current timeline items, along with a stream of updates of + /// timeline items. /// - /// This stream can yield multiple diffs at once. The batching is done such - /// that no arbitrary delays are added. - pub async fn subscribe_batched( + /// The stream produces `Vec>`, which means multiple updates + /// at once. There are no delays, it consumes as many updates as possible + /// and batches them. + pub async fn subscribe( &self, ) -> (Vector>, impl Stream>>>) { let (items, stream) = self.controller.subscribe_batched().await; diff --git a/crates/matrix-sdk-ui/tests/integration/room_list_service.rs b/crates/matrix-sdk-ui/tests/integration/room_list_service.rs index 587e5ca77..342ba7c9e 100644 --- a/crates/matrix-sdk-ui/tests/integration/room_list_service.rs +++ b/crates/matrix-sdk-ui/tests/integration/room_list_service.rs @@ -2430,7 +2430,7 @@ async fn test_room_timeline() -> Result<(), Error> { room.init_timeline_with_builder(room.default_room_timeline_builder().await.unwrap()).await?; let timeline = room.timeline().unwrap(); - let (previous_timeline_items, mut timeline_items_stream) = timeline.subscribe_batched().await; + let (previous_timeline_items, mut timeline_items_stream) = timeline.subscribe().await; sync_then_assert_request_and_fake_response! { [server, room_list, sync] @@ -2493,7 +2493,7 @@ async fn test_room_empty_timeline() { // The room wasn't synced, but it will be available let room = room_list.room(&room_id).unwrap(); let timeline = room.default_room_timeline_builder().await.unwrap().build().await.unwrap(); - let (prev_items, _) = timeline.subscribe_batched().await; + let (prev_items, _) = timeline.subscribe().await; // However, since the room wasn't synced its timeline won't have any initial // items diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/echo.rs b/crates/matrix-sdk-ui/tests/integration/timeline/echo.rs index b1fd848b9..0bc4c57d6 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/echo.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/echo.rs @@ -65,7 +65,7 @@ async fn test_echo() { .await .unwrap(), ); - let (_, mut timeline_stream) = timeline.subscribe_batched().await; + let (_, mut timeline_stream) = timeline.subscribe().await; let event_id = event_id!("$ev"); @@ -238,7 +238,7 @@ async fn test_dedup_by_event_id_late() { let room = client.get_room(room_id).unwrap(); let timeline = Arc::new(room.timeline().await.unwrap()); - let (_, mut timeline_stream) = timeline.subscribe_batched().await; + let (_, mut timeline_stream) = timeline.subscribe().await; let event_id = event_id!("$wWgymRfo7ri1uQx0NXO40vLJ"); diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/edit.rs b/crates/matrix-sdk-ui/tests/integration/timeline/edit.rs index 228100894..c53decc70 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/edit.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/edit.rs @@ -86,7 +86,7 @@ async fn test_edit() { let room = client.get_room(room_id).unwrap(); let timeline = room.timeline().await.unwrap(); - let (_, mut timeline_stream) = timeline.subscribe_batched().await; + let (_, mut timeline_stream) = timeline.subscribe().await; let event_id = event_id!("$msda7m:localhost"); sync_builder.add_joined_room( @@ -187,7 +187,7 @@ async fn test_edit_local_echo() { server.mock_room_state_encryption().plain().mount().await; let timeline = room.timeline().await.unwrap(); - let (_, mut timeline_stream) = timeline.subscribe_batched().await; + let (_, mut timeline_stream) = timeline.subscribe().await; let mounted_send = server.mock_room_send().error_too_large().mock_once().mount_as_scoped().await; @@ -758,7 +758,7 @@ async fn test_edit_local_echo_with_unsupported_content() { let room = client.get_room(room_id).unwrap(); let timeline = room.timeline().await.unwrap(); - let (_, mut timeline_stream) = timeline.subscribe_batched().await; + let (_, mut timeline_stream) = timeline.subscribe().await; sync_builder.add_joined_room(JoinedRoomBuilder::new(room_id)); @@ -916,7 +916,7 @@ async fn test_pending_edit() { let mut h = PendingEditHelper::new().await; let f = EventFactory::new(); - let (_, mut timeline_stream) = h.timeline.subscribe_batched().await; + let (_, mut timeline_stream) = h.timeline.subscribe().await; // When I receive an edit event for an event I don't know about… let original_event_id = event_id!("$original"); @@ -969,7 +969,7 @@ async fn test_pending_edit_overrides() { let mut h = PendingEditHelper::new().await; let f = EventFactory::new(); - let (_, mut timeline_stream) = h.timeline.subscribe_batched().await; + let (_, mut timeline_stream) = h.timeline.subscribe().await; // When I receive multiple edit events for an event I don't know about… let original_event_id = event_id!("$original"); @@ -1024,7 +1024,7 @@ async fn test_pending_edit_from_backpagination() { let mut h = PendingEditHelper::new().await; let f = EventFactory::new(); - let (_, mut timeline_stream) = h.timeline.subscribe_batched().await; + let (_, mut timeline_stream) = h.timeline.subscribe().await; // When I receive an edit from a back-pagination for an event I don't know // about… @@ -1090,7 +1090,7 @@ async fn test_pending_edit_from_backpagination_doesnt_override_pending_edit_from ) .await; - let (_, mut timeline_stream) = h.timeline.subscribe_batched().await; + let (_, mut timeline_stream) = h.timeline.subscribe().await; // And then I receive an edit from a back-pagination for the same event… let edit_event_id2 = event_id!("$edit2"); @@ -1139,7 +1139,7 @@ async fn test_pending_poll_edit() { let mut h = PendingEditHelper::new().await; let f = EventFactory::new(); - let (_, mut timeline_stream) = h.timeline.subscribe_batched().await; + let (_, mut timeline_stream) = h.timeline.subscribe().await; // When I receive an edit event for an event I don't know about… let original_event_id = event_id!("$original"); diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/focus_event.rs b/crates/matrix-sdk-ui/tests/integration/timeline/focus_event.rs index 0eb5e876e..f136a76bc 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/focus_event.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/focus_event.rs @@ -86,7 +86,7 @@ async fn test_new_focused() { server.reset().await; - let (items, mut timeline_stream) = timeline.subscribe_batched().await; + let (items, mut timeline_stream) = timeline.subscribe().await; assert_eq!(items.len(), 5 + 1); // event items + a date divider assert!(items[0].is_date_divider()); @@ -229,7 +229,7 @@ async fn test_focused_timeline_reacts() { server.reset().await; - let (items, mut timeline_stream) = timeline.subscribe_batched().await; + let (items, mut timeline_stream) = timeline.subscribe().await; assert_eq!(items.len(), 1 + 1); // event items + a date divider assert!(items[0].is_date_divider()); @@ -314,7 +314,7 @@ async fn test_focused_timeline_local_echoes() { server.reset().await; - let (items, mut timeline_stream) = timeline.subscribe_batched().await; + let (items, mut timeline_stream) = timeline.subscribe().await; assert_eq!(items.len(), 1 + 1); // event items + a date divider assert!(items[0].is_date_divider()); @@ -393,7 +393,7 @@ async fn test_focused_timeline_doesnt_show_local_echoes() { server.reset().await; - let (items, mut timeline_stream) = timeline.subscribe_batched().await; + let (items, mut timeline_stream) = timeline.subscribe().await; assert_eq!(items.len(), 1 + 1); // event items + a date divider assert!(items[0].is_date_divider()); diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/mod.rs b/crates/matrix-sdk-ui/tests/integration/timeline/mod.rs index 83f2bdb2e..23b065f97 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/mod.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/mod.rs @@ -79,7 +79,7 @@ async fn test_reaction() { let room = client.get_room(room_id).unwrap(); let timeline = room.timeline().await.unwrap(); - let (_, mut timeline_stream) = timeline.subscribe_batched().await; + let (_, mut timeline_stream) = timeline.subscribe().await; sync_builder.add_joined_room( JoinedRoomBuilder::new(room_id) @@ -190,7 +190,7 @@ async fn test_redacted_message() { let room = client.get_room(room_id).unwrap(); let timeline = room.timeline().await.unwrap(); - let (_, mut timeline_stream) = timeline.subscribe_batched().await; + let (_, mut timeline_stream) = timeline.subscribe().await; sync_builder.add_joined_room( JoinedRoomBuilder::new(room_id) @@ -248,7 +248,7 @@ async fn test_redact_message() { server.mock_room_state_encryption().plain().mount().await; let timeline = room.timeline().await.unwrap(); - let (_, mut timeline_stream) = timeline.subscribe_batched().await; + let (_, mut timeline_stream) = timeline.subscribe().await; let factory = EventFactory::new(); factory.set_next_ts(MilliSecondsSinceUnixEpoch::now().get().into()); @@ -327,7 +327,7 @@ async fn test_redact_local_sent_message() { server.mock_room_state_encryption().plain().mount().await; let timeline = room.timeline().await.unwrap(); - let (_, mut timeline_stream) = timeline.subscribe_batched().await; + let (_, mut timeline_stream) = timeline.subscribe().await; // Mock event sending. server.mock_room_send().ok(event_id!("$wWgymRfo7ri1uQx0NXO40vLJ")).mock_once().mount().await; @@ -420,7 +420,7 @@ async fn test_read_marker() { let room = client.get_room(room_id).unwrap(); let timeline = room.timeline().await.unwrap(); - let (_, mut timeline_stream) = timeline.subscribe_batched().await; + let (_, mut timeline_stream) = timeline.subscribe().await; sync_builder.add_joined_room(JoinedRoomBuilder::new(room_id).add_timeline_event( sync_timeline_event!({ @@ -510,7 +510,7 @@ async fn test_sync_highlighted() { let room = client.get_room(room_id).unwrap(); let timeline = room.timeline().await.unwrap(); - let (_, mut timeline_stream) = timeline.subscribe_batched().await; + let (_, mut timeline_stream) = timeline.subscribe().await; sync_builder.add_joined_room(JoinedRoomBuilder::new(room_id).add_timeline_event( sync_timeline_event!({ @@ -762,7 +762,7 @@ async fn test_timeline_without_encryption_info() { // Previously this would have panicked. let timeline = room.timeline().await.unwrap(); - let (items, _) = timeline.subscribe_batched().await; + let (items, _) = timeline.subscribe().await; assert_eq!(items.len(), 2); assert!(items[0].as_virtual().is_some()); // No encryption, no shields @@ -794,7 +794,7 @@ async fn test_timeline_without_encryption_can_update() { // encryption changes let timeline = Timeline::builder(&room).build().await.unwrap(); - let (items, mut stream) = timeline.subscribe_batched().await; + let (items, mut stream) = timeline.subscribe().await; assert_eq!(items.len(), 2); assert!(items[0].as_virtual().is_some()); // No encryption, no shields diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/pagination.rs b/crates/matrix-sdk-ui/tests/integration/timeline/pagination.rs index c833bea73..e8b5c5b3b 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/pagination.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/pagination.rs @@ -63,7 +63,7 @@ async fn test_back_pagination() { let room = client.get_room(room_id).unwrap(); let timeline = Arc::new(room.timeline().await.unwrap()); - let (_, mut timeline_stream) = timeline.subscribe_batched().await; + let (_, mut timeline_stream) = timeline.subscribe().await; let (_, mut back_pagination_status) = timeline.live_back_pagination_status().await.unwrap(); Mock::given(method("GET")) @@ -178,7 +178,7 @@ async fn test_back_pagination_highlighted() { let room = client.get_room(room_id).unwrap(); let timeline = Arc::new(room.timeline().await.unwrap()); - let (_, mut timeline_stream) = timeline.subscribe_batched().await; + let (_, mut timeline_stream) = timeline.subscribe().await; let response_json = json!({ "chunk": [ @@ -579,7 +579,7 @@ async fn test_empty_chunk() { let room = client.get_room(room_id).unwrap(); let timeline = Arc::new(room.timeline().await.unwrap()); - let (_, mut timeline_stream) = timeline.subscribe_batched().await; + let (_, mut timeline_stream) = timeline.subscribe().await; let (_, mut back_pagination_status) = timeline.live_back_pagination_status().await.unwrap(); // It should try to do another request after the empty chunk. @@ -681,7 +681,7 @@ async fn test_until_num_items_with_empty_chunk() { let room = client.get_room(room_id).unwrap(); let timeline = Arc::new(room.timeline().await.unwrap()); - let (_, mut timeline_stream) = timeline.subscribe_batched().await; + let (_, mut timeline_stream) = timeline.subscribe().await; let (_, mut back_pagination_status) = timeline.live_back_pagination_status().await.unwrap(); Mock::given(method("GET")) diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/pinned_event.rs b/crates/matrix-sdk-ui/tests/integration/timeline/pinned_event.rs index 943d6d5f5..4fc29373f 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/pinned_event.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/pinned_event.rs @@ -80,7 +80,7 @@ async fn test_new_pinned_events_are_added_on_sync() { ); // Load timeline items - let (items, mut timeline_stream) = timeline.subscribe_batched().await; + let (items, mut timeline_stream) = timeline.subscribe().await; assert_eq!(items.len(), 1 + 1); // event item + a date divider assert!(items[0].is_date_divider()); @@ -171,7 +171,7 @@ async fn test_new_pinned_event_ids_reload_the_timeline() { "there should be no live back-pagination status for a focused timeline" ); - let (items, mut timeline_stream) = timeline.subscribe_batched().await; + let (items, mut timeline_stream) = timeline.subscribe().await; assert_eq!(items.len(), 1 + 1); // event item + a date divider assert!(items[0].is_date_divider()); @@ -282,7 +282,7 @@ async fn test_cached_events_are_kept_for_different_room_instances() { "there should be no live back-pagination status for a focused timeline" ); - let (items, mut timeline_stream) = timeline.subscribe_batched().await; + let (items, mut timeline_stream) = timeline.subscribe().await; assert!(!items.is_empty()); // We just loaded some events assert_pending!(timeline_stream); @@ -306,7 +306,7 @@ async fn test_cached_events_are_kept_for_different_room_instances() { let timeline = Timeline::builder(&room).with_focus(pinned_events_focus(2)).build().await.unwrap(); - let (items, _) = timeline.subscribe_batched().await; + let (items, _) = timeline.subscribe().await; assert!(!items.is_empty()); // These events came from the cache assert!(room_cache.event(event_id!("$1")).await.is_some()); @@ -367,7 +367,7 @@ async fn test_pinned_timeline_with_no_pinned_event_ids_is_just_empty() { // The timeline couldn't load any events, but it expected none, so it just // returns an empty list - let (items, _) = timeline.subscribe_batched().await; + let (items, _) = timeline.subscribe().await; assert!(items.is_empty()); } @@ -395,7 +395,7 @@ async fn test_pinned_timeline_with_no_pinned_events_and_an_utd_on_sync_is_just_e // The timeline couldn't load any events, but it expected none, so it just // returns an empty list - let (items, _) = timeline.subscribe_batched().await; + let (items, _) = timeline.subscribe().await; assert!(items.is_empty()); } @@ -418,7 +418,7 @@ async fn test_pinned_timeline_with_no_pinned_events_on_pagination_is_just_empty( // The timeline couldn't load any events, but it expected none, so it just // returns an empty list - let (pinned_items, mut pinned_events_stream) = pinned_timeline.subscribe_batched().await; + let (pinned_items, mut pinned_events_stream) = pinned_timeline.subscribe().await; assert!(pinned_items.is_empty()); // Create a non-pinned event to return in the pagination @@ -485,7 +485,7 @@ async fn test_pinned_timeline_with_pinned_utd_on_sync_contains_it() { Timeline::builder(&room).with_focus(pinned_events_focus(1)).build().await.unwrap(); // The timeline loaded with just a day divider and the pinned UTD - let (items, _) = timeline.subscribe_batched().await; + let (items, _) = timeline.subscribe().await; assert_eq!(items.len(), 2); let pinned_utd_event = items.last().unwrap().as_event().unwrap(); assert_eq!(pinned_utd_event.event_id().unwrap(), event_id); @@ -523,7 +523,7 @@ async fn test_edited_events_are_reflected_in_sync() { ); // Load timeline items - let (items, mut timeline_stream) = timeline.subscribe_batched().await; + let (items, mut timeline_stream) = timeline.subscribe().await; assert_eq!(items.len(), 1 + 1); // event item + a date divider assert!(items[0].is_date_divider()); @@ -609,7 +609,7 @@ async fn test_redacted_events_are_reflected_in_sync() { ); // Load timeline items - let (items, mut timeline_stream) = timeline.subscribe_batched().await; + let (items, mut timeline_stream) = timeline.subscribe().await; assert_eq!(items.len(), 1 + 1); // event item + a date divider assert!(items[0].is_date_divider()); @@ -685,7 +685,7 @@ async fn test_edited_events_survive_pinned_event_ids_change() { ); // Load timeline items - let (items, mut timeline_stream) = timeline.subscribe_batched().await; + let (items, mut timeline_stream) = timeline.subscribe().await; assert_eq!(items.len(), 1 + 1); // event item + a date divider assert!(items[0].is_date_divider()); diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/queue.rs b/crates/matrix-sdk-ui/tests/integration/timeline/queue.rs index 08184b1ba..4a7ae1f08 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/queue.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/queue.rs @@ -329,7 +329,7 @@ async fn test_clear_with_echoes() { // Send a message without mocking the server response. { - let (_, mut timeline_stream) = timeline.subscribe_batched().await; + let (_, mut timeline_stream) = timeline.subscribe().await; timeline.send(RoomMessageEventContent::text_plain("Send failure").into()).await.unwrap(); @@ -408,7 +408,7 @@ async fn test_no_duplicate_date_divider() { let room = client.get_room(room_id).unwrap(); let timeline = Arc::new(room.timeline().await.unwrap()); - let (_, mut timeline_stream) = timeline.subscribe_batched().await; + let (_, mut timeline_stream) = timeline.subscribe().await; // Response for first message takes 200ms to respond. Mock::given(method("PUT")) diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/reactions.rs b/crates/matrix-sdk-ui/tests/integration/timeline/reactions.rs index 23dc827b2..24994a5be 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/reactions.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/reactions.rs @@ -47,7 +47,7 @@ async fn test_abort_before_being_sent() { server.mock_room_state_encryption().plain().mount().await; let timeline = room.timeline().await.unwrap(); - let (initial_items, mut stream) = timeline.subscribe_batched().await; + let (initial_items, mut stream) = timeline.subscribe().await; assert!(initial_items.is_empty()); @@ -202,7 +202,7 @@ async fn test_redact_failed() { let room = client.get_room(room_id).unwrap(); let timeline = room.timeline().await.unwrap(); - let (initial_items, mut stream) = timeline.subscribe_batched().await; + let (initial_items, mut stream) = timeline.subscribe().await; assert!(initial_items.is_empty()); @@ -288,7 +288,7 @@ async fn test_local_reaction_to_local_echo() { let room = client.get_room(room_id).unwrap(); let timeline = room.timeline().await.unwrap(); - let (initial_items, mut stream) = timeline.subscribe_batched().await; + let (initial_items, mut stream) = timeline.subscribe().await; assert!(initial_items.is_empty()); diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/read_receipts.rs b/crates/matrix-sdk-ui/tests/integration/timeline/read_receipts.rs index 8a36c89c3..837884892 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/read_receipts.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/read_receipts.rs @@ -76,7 +76,7 @@ async fn test_read_receipts_updates() { let room = client.get_room(room_id).unwrap(); let timeline = room.timeline().await.unwrap(); - let (items, mut timeline_stream) = timeline.subscribe_batched().await; + let (items, mut timeline_stream) = timeline.subscribe().await; let mut own_receipts_subscriber = timeline.subscribe_own_user_read_receipts_changed().await; assert!(items.is_empty()); @@ -319,7 +319,7 @@ async fn test_read_receipts_updates_on_filtered_events() { let room = client.get_room(room_id).unwrap(); let timeline = room.timeline_builder().event_filter(filter_notice).build().await.unwrap(); - let (items, mut timeline_stream) = timeline.subscribe_batched().await; + let (items, mut timeline_stream) = timeline.subscribe().await; assert!(items.is_empty()); @@ -1206,7 +1206,7 @@ async fn test_latest_user_read_receipt() { let room = client.get_room(room_id).unwrap(); let timeline = room.timeline().await.unwrap(); - let (items, _) = timeline.subscribe_batched().await; + let (items, _) = timeline.subscribe().await; assert!(items.is_empty()); diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/replies.rs b/crates/matrix-sdk-ui/tests/integration/timeline/replies.rs index 592df6ba7..d00b2ae3e 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/replies.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/replies.rs @@ -50,7 +50,7 @@ async fn test_in_reply_to_details() { let room = client.get_room(room_id).unwrap(); let timeline = room.timeline().await.unwrap(); - let (_, mut timeline_stream) = timeline.subscribe_batched().await; + let (_, mut timeline_stream) = timeline.subscribe().await; // The event doesn't exist. assert_matches!( diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/sliding_sync.rs b/crates/matrix-sdk-ui/tests/integration/timeline/sliding_sync.rs index 161daa264..42cacc4f0 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/sliding_sync.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/sliding_sync.rs @@ -289,7 +289,7 @@ async fn timeline_test_helper( let timeline = Timeline::builder(&sdk_room).track_read_marker_and_receipts().build().await?; - Ok(timeline.subscribe_batched().await) + Ok(timeline.subscribe().await) } struct SlidingSyncMatcher; diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/subscribe.rs b/crates/matrix-sdk-ui/tests/integration/timeline/subscribe.rs index 778cba3d9..b6c57f82e 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/subscribe.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/subscribe.rs @@ -52,7 +52,7 @@ async fn test_batched() { let room = client.get_room(room_id).unwrap(); let timeline = room.timeline_builder().event_filter(|_, _| true).build().await.unwrap(); - let (_, mut timeline_stream) = timeline.subscribe_batched().await; + let (_, mut timeline_stream) = timeline.subscribe().await; let hdl = tokio::spawn(async move { let next_batch = timeline_stream.next().await.unwrap(); @@ -93,7 +93,7 @@ async fn test_event_filter() { let room = client.get_room(room_id).unwrap(); let timeline = room.timeline_builder().event_filter(|_, _| true).build().await.unwrap(); - let (_, mut timeline_stream) = timeline.subscribe_batched().await; + let (_, mut timeline_stream) = timeline.subscribe().await; let first_event_id = event_id!("$YTQwYl2ply"); sync_builder.add_joined_room(JoinedRoomBuilder::new(room_id).add_timeline_event( @@ -211,7 +211,7 @@ async fn test_timeline_is_reset_when_a_user_is_ignored_or_unignored() { let room = client.get_room(room_id).unwrap(); let timeline = room.timeline_builder().build().await.unwrap(); - let (_, mut timeline_stream) = timeline.subscribe_batched().await; + let (_, mut timeline_stream) = timeline.subscribe().await; let alice = user_id!("@alice:example.org"); let bob = user_id!("@bob:example.org"); @@ -329,7 +329,7 @@ async fn test_profile_updates() { let room = client.get_room(room_id).unwrap(); let timeline = room.timeline_builder().build().await.unwrap(); - let (_, mut timeline_stream) = timeline.subscribe_batched().await; + let (_, mut timeline_stream) = timeline.subscribe().await; let alice = "@alice:example.org"; let bob = "@bob:example.org"; diff --git a/examples/timeline/src/main.rs b/examples/timeline/src/main.rs index cf5c1157e..5b3d2bc40 100644 --- a/examples/timeline/src/main.rs +++ b/examples/timeline/src/main.rs @@ -71,7 +71,7 @@ async fn main() -> Result<()> { // Get the timeline stream and listen to it. let room = client.get_room(&room_id).unwrap(); let timeline = room.timeline().await.unwrap(); - let (timeline_items, mut timeline_stream) = timeline.subscribe_batched().await; + let (timeline_items, mut timeline_stream) = timeline.subscribe().await; println!("Initial timeline items: {timeline_items:#?}"); tokio::spawn(async move { diff --git a/labs/multiverse/src/main.rs b/labs/multiverse/src/main.rs index 873d1aae6..eb18f2f4c 100644 --- a/labs/multiverse/src/main.rs +++ b/labs/multiverse/src/main.rs @@ -267,7 +267,7 @@ impl App { // Save the timeline in the cache. let sdk_timeline = ui_room.timeline().unwrap(); - let (items, stream) = sdk_timeline.subscribe_batched().await; + let (items, stream) = sdk_timeline.subscribe().await; let items = Arc::new(Mutex::new(items)); // Spawn a timeline task that will listen to all the timeline item changes. diff --git a/testing/matrix-sdk-integration-testing/src/tests/sliding_sync/room.rs b/testing/matrix-sdk-integration-testing/src/tests/sliding_sync/room.rs index 0bfd46359..fa7b3296a 100644 --- a/testing/matrix-sdk-integration-testing/src/tests/sliding_sync/room.rs +++ b/testing/matrix-sdk-integration-testing/src/tests/sliding_sync/room.rs @@ -893,7 +893,7 @@ async fn test_delayed_invite_response_and_sent_message_decryption() { // Join the room from Bob's client. let bob_timeline = bob_room.timeline().await.unwrap(); - let (_, timeline_stream) = bob_timeline.subscribe_batched().await; + let (_, timeline_stream) = bob_timeline.subscribe().await; pin_mut!(timeline_stream); info!("Bob joins the room."); diff --git a/testing/matrix-sdk-integration-testing/src/tests/timeline.rs b/testing/matrix-sdk-integration-testing/src/tests/timeline.rs index 8f8c81ce8..a822a4142 100644 --- a/testing/matrix-sdk-integration-testing/src/tests/timeline.rs +++ b/testing/matrix-sdk-integration-testing/src/tests/timeline.rs @@ -91,7 +91,7 @@ async fn test_toggling_reaction() -> Result<()> { // waiting for it. let timeline = room.timeline().await.unwrap(); - let (mut items, mut stream) = timeline.subscribe_batched().await; + let (mut items, mut stream) = timeline.subscribe().await; let event_id_task: JoinHandle> = spawn(async move { let find_event_id = |items: &Vector>| { @@ -146,7 +146,7 @@ async fn test_toggling_reaction() -> Result<()> { // Give a bit of time for the timeline to process all sync updates. sleep(Duration::from_secs(1)).await; - let (mut items, mut stream) = timeline.subscribe_batched().await; + let (mut items, mut stream) = timeline.subscribe().await; // Skip all stream updates that have happened so far. debug!("Skipping all other stream updates…"); From 1d3f8bf8981e42341c6dc228ce2783a32f5eb2bd Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 27 Jan 2025 17:18:45 +0100 Subject: [PATCH 36/55] doc(ui): Update the `CHANGELOG`. --- crates/matrix-sdk-ui/CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/matrix-sdk-ui/CHANGELOG.md b/crates/matrix-sdk-ui/CHANGELOG.md index 2b84ee3f6..201b2a8c5 100644 --- a/crates/matrix-sdk-ui/CHANGELOG.md +++ b/crates/matrix-sdk-ui/CHANGELOG.md @@ -30,6 +30,11 @@ All notable changes to this project will be documented in this file. are unified to work on a live or focused timeline. `Timeline::live_paginate_*` and `Timeline::focused_paginate_*` have been removed ([#4584](https://github.com/matrix-org/matrix-rust-sdk/pull/4584)). +- [**breaking**] `Timeline::subscribe_batched` replaces + `Timeline::subscribe`. `subscribe` has been removed in + [#4567](https://github.com/matrix-org/matrix-rust-sdk/pull/4567), + and `subscribe_batched` has been renamed to `subscribe` in + [#4585](https://github.com/matrix-org/matrix-rust-sdk/pull/4585). ## [0.9.0] - 2024-12-18 From 173ec75bb33c8a1bd8ace5c2c59678f69e88de9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 16 Jan 2025 13:29:36 +0100 Subject: [PATCH 37/55] refactor(ui): Move common data of the SyncService under a lock Previously we had a lock protecting an empty value, but the logic wants to protect a bunch of data in the SyncService. Let's do the usual thing and create a SyncServiceInner which holds the data and protect that with a lock. --- crates/matrix-sdk-ui/src/sync_service.rs | 143 +++++++++--------- .../tests/integration/sync_service.rs | 10 +- 2 files changed, 74 insertions(+), 79 deletions(-) diff --git a/crates/matrix-sdk-ui/src/sync_service.rs b/crates/matrix-sdk-ui/src/sync_service.rs index 752d3eb6f..8ace8da86 100644 --- a/crates/matrix-sdk-ui/src/sync_service.rs +++ b/crates/matrix-sdk-ui/src/sync_service.rs @@ -23,7 +23,7 @@ //! MUST observe. Whenever an error/termination is observed, the user MUST call //! [`SyncService::start()`] again to restart the room list sync. -use std::sync::{Arc, Mutex}; +use std::sync::Arc; use eyeball::{SharedObservable, Subscriber}; use futures_core::Future; @@ -67,35 +67,11 @@ pub enum State { Error, } -pub struct SyncService { - /// Room list service used to synchronize the rooms state. +struct SyncServiceInner { room_list_service: Arc, - - /// Encryption sync taking care of e2ee events. encryption_sync_service: Arc, - - /// What's the state of this sync service? state: SharedObservable, - - /// Use a mutex everytime to modify the `state` value, otherwise it would be - /// possible to have race conditions when starting or pausing the - /// service multiple times really quickly. - modifying_state: AsyncMutex<()>, - - /// Task running the room list service. - room_list_task: Arc>>>, - - /// Task running the encryption sync. - encryption_sync_task: Arc>>>, - - /// Global lock to allow using at most one `EncryptionSyncService` at all - /// times. - /// - /// This ensures that there's only one ever existing in the application's - /// lifetime (under the assumption that there is at most one - /// `SyncService` per application). encryption_sync_permit: Arc>, - /// Scheduler task ensuring proper termination. /// /// This task is waiting for a `TerminationReport` from any of the other two @@ -103,12 +79,32 @@ pub struct SyncService { /// that the two services are properly shut up and just interrupted. /// /// This is set at the same time as the other two tasks. - scheduler_task: Arc>>>, - + scheduler_task: Option>, /// `TerminationReport` sender for the [`Self::stop()`] function. /// /// This is set at the same time as all the tasks in [`Self::start()`]. - scheduler_sender: Mutex>>, + scheduler_sender: Option>, +} + +pub struct SyncService { + inner: Arc>, + + /// Room list service used to synchronize the rooms state. + room_list_service: Arc, + + /// What's the state of this sync service? This field is replicated from the + /// [`SyncServiceInner`] struct, but it should not be modified in this + /// struct. It's re-exposed here so we can subscribe to the state + /// without taking the lock on the `inner` field. + state: SharedObservable, + + /// Global lock to allow using at most one [`EncryptionSyncService`] at all + /// times. + /// + /// This ensures that there's only one ever existing in the application's + /// lifetime (under the assumption that there is at most one [`SyncService`] + /// per application). + encryption_sync_permit: Arc>, } impl SyncService { @@ -134,13 +130,14 @@ impl SyncService { /// the other one too). fn spawn_scheduler_task( &self, + inner: &SyncServiceInner, + room_list_task: JoinHandle<()>, + encryption_sync_task: JoinHandle<()>, mut receiver: Receiver, ) -> impl Future { - let encryption_sync_task = self.encryption_sync_task.clone(); - let encryption_sync = self.encryption_sync_service.clone(); - let room_list_service = self.room_list_service.clone(); - let room_list_task = self.room_list_task.clone(); - let state = self.state.clone(); + let encryption_sync = inner.encryption_sync_service.clone(); + let room_list_service = inner.room_list_service.clone(); + let state = inner.state.clone(); async move { let Some(report) = receiver.recv().await else { @@ -165,13 +162,8 @@ impl SyncService { } } - { - let task = room_list_task.lock().unwrap().take(); - if let Some(task) = task { - if let Err(err) = task.await { - error!("when awaiting room list service: {err:#}"); - } - } + if let Err(err) = room_list_task.await { + error!("when awaiting room list service: {err:#}"); } if stop_encryption { @@ -180,13 +172,8 @@ impl SyncService { } } - { - let task = encryption_sync_task.lock().unwrap().take(); - if let Some(task) = task { - if let Err(err) = task.await { - error!("when awaiting encryption sync: {err:#}"); - } - } + if let Err(err) = encryption_sync_task.await { + error!("when awaiting encryption sync: {err:#}"); } if report.is_error { @@ -306,7 +293,7 @@ impl SyncService { /// - if the stream has been aborted before, it will be properly cleaned up /// and restarted. pub async fn start(&self) { - let _guard = self.modifying_state.lock().await; + let mut inner = self.inner.lock().await; // Only (re)start the tasks if any was stopped. if matches!(self.state.get(), State::Running) { @@ -319,20 +306,25 @@ impl SyncService { let (sender, receiver) = tokio::sync::mpsc::channel(16); // First, take care of the room list. - *self.room_list_task.lock().unwrap() = - Some(spawn(Self::room_list_sync_task(self.room_list_service.clone(), sender.clone()))); + let room_list_task = + spawn(Self::room_list_sync_task(self.room_list_service.clone(), sender.clone())); // Then, take care of the encryption sync. - let sync_permit_guard = self.encryption_sync_permit.clone().lock_owned().await; - *self.encryption_sync_task.lock().unwrap() = Some(spawn(Self::encryption_sync_task( - self.encryption_sync_service.clone(), + let sync_permit_guard = inner.encryption_sync_permit.clone().lock_owned().await; + let encryption_sync_task = spawn(Self::encryption_sync_task( + inner.encryption_sync_service.clone(), sender.clone(), sync_permit_guard, - ))); + )); // Spawn the scheduler task. - *self.scheduler_sender.lock().unwrap() = Some(sender); - *self.scheduler_task.lock().unwrap() = Some(spawn(self.spawn_scheduler_task(receiver))); + inner.scheduler_sender = Some(sender); + inner.scheduler_task = Some(spawn(self.spawn_scheduler_task( + &inner, + room_list_task, + encryption_sync_task, + receiver, + ))); self.state.set(State::Running); } @@ -344,7 +336,7 @@ impl SyncService { /// necessary. #[instrument(skip_all)] pub async fn stop(&self) -> Result<(), Error> { - let _guard = self.modifying_state.lock().await; + let mut inner = self.inner.lock().await; match self.state.get() { State::Idle | State::Terminated | State::Error => { @@ -360,7 +352,7 @@ impl SyncService { // later, so that we're in a clean state independently of the request to // stop. - let sender = self.scheduler_sender.lock().unwrap().clone(); + let sender = inner.scheduler_sender.clone(); sender .ok_or_else(|| { error!("missing sender"); @@ -377,7 +369,7 @@ impl SyncService { Error::InternalSchedulerError })?; - let scheduler_task = self.scheduler_task.lock().unwrap().take(); + let scheduler_task = inner.scheduler_task.take(); scheduler_task .ok_or_else(|| { error!("missing scheduler task"); @@ -420,11 +412,8 @@ struct TerminationReport { #[doc(hidden)] impl SyncService { /// Return the existential states of internal tasks. - pub fn task_states(&self) -> (bool, bool) { - ( - self.encryption_sync_task.lock().unwrap().is_some(), - self.room_list_task.lock().unwrap().is_some(), - ) + pub async fn task_state(&self) -> bool { + self.inner.lock().await.scheduler_task.is_some() } } @@ -476,16 +465,22 @@ impl SyncServiceBuilder { .await?, ); + let room_list_service = Arc::new(room_list); + let state = SharedObservable::new(State::Idle); + Ok(SyncService { - room_list_service: Arc::new(room_list), - encryption_sync_service: encryption_sync, - encryption_sync_task: Arc::new(Mutex::new(None)), - room_list_task: Arc::new(Mutex::new(None)), - scheduler_task: Arc::new(Mutex::new(None)), - scheduler_sender: Mutex::new(None), - state: SharedObservable::new(State::Idle), - modifying_state: AsyncMutex::new(()), - encryption_sync_permit, + state: state.clone(), + room_list_service: room_list_service.clone(), + encryption_sync_permit: encryption_sync_permit.clone(), + + inner: Arc::new(AsyncMutex::new(SyncServiceInner { + scheduler_task: None, + scheduler_sender: None, + room_list_service, + encryption_sync_service: encryption_sync, + state, + encryption_sync_permit, + })), }) } } diff --git a/crates/matrix-sdk-ui/tests/integration/sync_service.rs b/crates/matrix-sdk-ui/tests/integration/sync_service.rs index dae23a426..80a1d1ef6 100644 --- a/crates/matrix-sdk-ui/tests/integration/sync_service.rs +++ b/crates/matrix-sdk-ui/tests/integration/sync_service.rs @@ -73,19 +73,19 @@ async fn test_sync_service_state() -> anyhow::Result<()> { // At first, the sync service is sleeping. assert_eq!(state_stream.get(), State::Idle); assert!(server.received_requests().await.unwrap().is_empty()); - assert_eq!(sync_service.task_states(), (false, false)); + assert_eq!(sync_service.task_state().await, false); assert!(sync_service.try_get_encryption_sync_permit().is_some()); // After starting, the sync service is, well, running. sync_service.start().await; assert_next_matches!(state_stream, State::Running); - assert_eq!(sync_service.task_states(), (true, true)); + assert_eq!(sync_service.task_state().await, true); assert!(sync_service.try_get_encryption_sync_permit().is_none()); // Restarting while started doesn't change the current state. sync_service.start().await; assert_pending!(state_stream); - assert_eq!(sync_service.task_states(), (true, true)); + assert_eq!(sync_service.task_state().await, true); assert!(sync_service.try_get_encryption_sync_permit().is_none()); // Let the server respond a few times. @@ -94,7 +94,7 @@ async fn test_sync_service_state() -> anyhow::Result<()> { // Pausing will stop both syncs, after a bit of delay. sync_service.stop().await?; assert_next_matches!(state_stream, State::Idle); - assert_eq!(sync_service.task_states(), (false, false)); + assert_eq!(sync_service.task_state().await, false); assert!(sync_service.try_get_encryption_sync_permit().is_some()); let mut num_encryption_sync_requests: i32 = 0; @@ -149,7 +149,7 @@ async fn test_sync_service_state() -> anyhow::Result<()> { // the same position than just before being stopped. sync_service.start().await; assert_next_matches!(state_stream, State::Running); - assert_eq!(sync_service.task_states(), (true, true)); + assert_eq!(sync_service.task_state().await, true); assert!(sync_service.try_get_encryption_sync_permit().is_none()); tokio::time::sleep(Duration::from_millis(100)).await; From 4344e06707e9436dae82e4e7d1600acea7c8fedf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 16 Jan 2025 13:40:04 +0100 Subject: [PATCH 38/55] refactor(ui): Rename the scheduler task to supervisor task From cambridge a scheduler is defined as: > someone whose job is to create or work with schedules While supervisor is defined as: > a person whose job is to supervise someone or something Well ok, that doesn't tell us much, supervise is defined as: > to watch a person or activity to make certain that everything is done correctly, safely, etc.: In conclusion, supervising a task is the more common and better understood terminology here I would say. --- crates/matrix-sdk-ui/src/sync_service.rs | 56 +++++++++---------- .../tests/integration/sync_service.rs | 10 ++-- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/crates/matrix-sdk-ui/src/sync_service.rs b/crates/matrix-sdk-ui/src/sync_service.rs index 8ace8da86..1fcf475f1 100644 --- a/crates/matrix-sdk-ui/src/sync_service.rs +++ b/crates/matrix-sdk-ui/src/sync_service.rs @@ -72,18 +72,18 @@ struct SyncServiceInner { encryption_sync_service: Arc, state: SharedObservable, encryption_sync_permit: Arc>, - /// Scheduler task ensuring proper termination. + /// Supervisor task ensuring proper termination. /// /// This task is waiting for a `TerminationReport` from any of the other two /// tasks, or from a user request via [`Self::stop()`]. It makes sure /// that the two services are properly shut up and just interrupted. /// /// This is set at the same time as the other two tasks. - scheduler_task: Option>, + supervisor_task: Option>, /// `TerminationReport` sender for the [`Self::stop()`] function. /// /// This is set at the same time as all the tasks in [`Self::start()`]. - scheduler_sender: Option>, + supervisor_sender: Option>, } pub struct SyncService { @@ -124,11 +124,11 @@ impl SyncService { self.state.subscribe() } - /// The role of the scheduler task is to wait for a termination message + /// The role of the supervisor task is to wait for a termination message /// (`TerminationReport`), sent either because we wanted to stop both /// syncs, or because one of the syncs failed (in which case we'll stop /// the other one too). - fn spawn_scheduler_task( + fn spawn_supervisor_task( &self, inner: &SyncServiceInner, room_list_task: JoinHandle<()>, @@ -149,7 +149,7 @@ impl SyncService { let (stop_room_list, stop_encryption) = match &report.origin { TerminationOrigin::EncryptionSync => (true, false), TerminationOrigin::RoomList => (false, true), - TerminationOrigin::Scheduler => (true, true), + TerminationOrigin::Supervisor => (true, true), }; // Stop both services, and wait for the streams to properly finish: at some @@ -187,13 +187,13 @@ impl SyncService { } state.set(State::Error); - } else if matches!(report.origin, TerminationOrigin::Scheduler) { + } else if matches!(report.origin, TerminationOrigin::Supervisor) { state.set(State::Idle); } else { state.set(State::Terminated); } } - .instrument(tracing::span!(Level::WARN, "scheduler task")) + .instrument(tracing::span!(Level::WARN, "supervisor task")) } async fn encryption_sync_task( @@ -317,9 +317,9 @@ impl SyncService { sync_permit_guard, )); - // Spawn the scheduler task. - inner.scheduler_sender = Some(sender); - inner.scheduler_task = Some(spawn(self.spawn_scheduler_task( + // Spawn the supervisor task. + inner.supervisor_sender = Some(sender); + inner.supervisor_task = Some(spawn(self.spawn_supervisor_task( &inner, room_list_task, encryption_sync_task, @@ -352,33 +352,33 @@ impl SyncService { // later, so that we're in a clean state independently of the request to // stop. - let sender = inner.scheduler_sender.clone(); + let sender = inner.supervisor_sender.clone(); sender .ok_or_else(|| { error!("missing sender"); - Error::InternalSchedulerError + Error::InternalSupervisorError })? .send(TerminationReport { is_error: false, has_expired: false, - origin: TerminationOrigin::Scheduler, + origin: TerminationOrigin::Supervisor, }) .await .map_err(|err| { error!("when sending termination report: {err}"); - Error::InternalSchedulerError + Error::InternalSupervisorError })?; - let scheduler_task = inner.scheduler_task.take(); - scheduler_task + let supervisor_task = inner.supervisor_task.take(); + supervisor_task .ok_or_else(|| { - error!("missing scheduler task"); - Error::InternalSchedulerError + error!("missing supervisor task"); + Error::InternalSupervisorError })? .await .map_err(|err| { - error!("couldn't finish scheduler task: {err}"); - Error::InternalSchedulerError + error!("couldn't finish supervisor task: {err}"); + Error::InternalSupervisorError })?; Ok(()) @@ -398,7 +398,7 @@ impl SyncService { enum TerminationOrigin { EncryptionSync, RoomList, - Scheduler, + Supervisor, } #[derive(Debug)] @@ -412,8 +412,8 @@ struct TerminationReport { #[doc(hidden)] impl SyncService { /// Return the existential states of internal tasks. - pub async fn task_state(&self) -> bool { - self.inner.lock().await.scheduler_task.is_some() + pub async fn is_supervisor_running(&self) -> bool { + self.inner.lock().await.supervisor_task.is_some() } } @@ -474,8 +474,8 @@ impl SyncServiceBuilder { encryption_sync_permit: encryption_sync_permit.clone(), inner: Arc::new(AsyncMutex::new(SyncServiceInner { - scheduler_task: None, - scheduler_sender: None, + supervisor_task: None, + supervisor_sender: None, room_list_service, encryption_sync_service: encryption_sync, state, @@ -496,6 +496,6 @@ pub enum Error { #[error(transparent)] EncryptionSync(#[from] encryption_sync_service::Error), - #[error("the scheduler channel has run into an unexpected error")] - InternalSchedulerError, + #[error("the supervisor channel has run into an unexpected error")] + InternalSupervisorError, } diff --git a/crates/matrix-sdk-ui/tests/integration/sync_service.rs b/crates/matrix-sdk-ui/tests/integration/sync_service.rs index 80a1d1ef6..6c28f3190 100644 --- a/crates/matrix-sdk-ui/tests/integration/sync_service.rs +++ b/crates/matrix-sdk-ui/tests/integration/sync_service.rs @@ -73,19 +73,19 @@ async fn test_sync_service_state() -> anyhow::Result<()> { // At first, the sync service is sleeping. assert_eq!(state_stream.get(), State::Idle); assert!(server.received_requests().await.unwrap().is_empty()); - assert_eq!(sync_service.task_state().await, false); + assert!(!sync_service.is_supervisor_running().await); assert!(sync_service.try_get_encryption_sync_permit().is_some()); // After starting, the sync service is, well, running. sync_service.start().await; assert_next_matches!(state_stream, State::Running); - assert_eq!(sync_service.task_state().await, true); + assert!(sync_service.is_supervisor_running().await); assert!(sync_service.try_get_encryption_sync_permit().is_none()); // Restarting while started doesn't change the current state. sync_service.start().await; assert_pending!(state_stream); - assert_eq!(sync_service.task_state().await, true); + assert!(sync_service.is_supervisor_running().await); assert!(sync_service.try_get_encryption_sync_permit().is_none()); // Let the server respond a few times. @@ -94,7 +94,7 @@ async fn test_sync_service_state() -> anyhow::Result<()> { // Pausing will stop both syncs, after a bit of delay. sync_service.stop().await?; assert_next_matches!(state_stream, State::Idle); - assert_eq!(sync_service.task_state().await, false); + assert!(!sync_service.is_supervisor_running().await); assert!(sync_service.try_get_encryption_sync_permit().is_some()); let mut num_encryption_sync_requests: i32 = 0; @@ -149,7 +149,7 @@ async fn test_sync_service_state() -> anyhow::Result<()> { // the same position than just before being stopped. sync_service.start().await; assert_next_matches!(state_stream, State::Running); - assert_eq!(sync_service.task_state().await, true); + assert!(sync_service.is_supervisor_running().await); assert!(sync_service.try_get_encryption_sync_permit().is_none()); tokio::time::sleep(Duration::from_millis(100)).await; From 3085f05d510e744d5d6e704cd0b667b1969d8e77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 16 Jan 2025 14:18:42 +0100 Subject: [PATCH 39/55] refactor(ui): Create a Supervisor for the SyncService The supervisor is defined as two optional fields that are set and removed at the same time. This patch converts the two optional fields into a single optional struct. The fields inside the struct now aren't anymore optional. This ensures that they are always set and destroyed at the same time. --- crates/matrix-sdk-ui/src/sync_service.rs | 200 +++++++++++++---------- 1 file changed, 111 insertions(+), 89 deletions(-) diff --git a/crates/matrix-sdk-ui/src/sync_service.rs b/crates/matrix-sdk-ui/src/sync_service.rs index 1fcf475f1..adfdef3f1 100644 --- a/crates/matrix-sdk-ui/src/sync_service.rs +++ b/crates/matrix-sdk-ui/src/sync_service.rs @@ -67,61 +67,33 @@ pub enum State { Error, } -struct SyncServiceInner { - room_list_service: Arc, - encryption_sync_service: Arc, - state: SharedObservable, - encryption_sync_permit: Arc>, - /// Supervisor task ensuring proper termination. - /// - /// This task is waiting for a `TerminationReport` from any of the other two - /// tasks, or from a user request via [`Self::stop()`]. It makes sure - /// that the two services are properly shut up and just interrupted. - /// - /// This is set at the same time as the other two tasks. - supervisor_task: Option>, +/// A supervisor that starts two sync tasks, one for the room list and one for +/// the end-to-end encryption support. +struct SyncTaskSupervisor { + /// The task that supervises the two sync tasks. + task: JoinHandle<()>, /// `TerminationReport` sender for the [`Self::stop()`] function. /// /// This is set at the same time as all the tasks in [`Self::start()`]. - supervisor_sender: Option>, + abortion_sender: Sender, } -pub struct SyncService { - inner: Arc>, +impl SyncTaskSupervisor { + async fn new( + inner: &SyncServiceInner, + room_list_task: JoinHandle<()>, + encryption_sync_task: JoinHandle<()>, + sender: Sender, + receiver: Receiver, + ) -> Self { + let task = spawn(Self::spawn_supervisor_task( + inner, + room_list_task, + encryption_sync_task, + receiver, + )); - /// Room list service used to synchronize the rooms state. - room_list_service: Arc, - - /// What's the state of this sync service? This field is replicated from the - /// [`SyncServiceInner`] struct, but it should not be modified in this - /// struct. It's re-exposed here so we can subscribe to the state - /// without taking the lock on the `inner` field. - state: SharedObservable, - - /// Global lock to allow using at most one [`EncryptionSyncService`] at all - /// times. - /// - /// This ensures that there's only one ever existing in the application's - /// lifetime (under the assumption that there is at most one [`SyncService`] - /// per application). - encryption_sync_permit: Arc>, -} - -impl SyncService { - /// Create a new builder for configuring an `SyncService`. - pub fn builder(client: Client) -> SyncServiceBuilder { - SyncServiceBuilder::new(client) - } - - /// Get the underlying `RoomListService` instance for easier access to its - /// methods. - pub fn room_list_service(&self) -> Arc { - self.room_list_service.clone() - } - - /// Returns the state of the sync service. - pub fn state(&self) -> Subscriber { - self.state.subscribe() + Self { task, abortion_sender: sender } } /// The role of the supervisor task is to wait for a termination message @@ -129,7 +101,6 @@ impl SyncService { /// syncs, or because one of the syncs failed (in which case we'll stop /// the other one too). fn spawn_supervisor_task( - &self, inner: &SyncServiceInner, room_list_task: JoinHandle<()>, encryption_sync_task: JoinHandle<()>, @@ -196,6 +167,83 @@ impl SyncService { .instrument(tracing::span!(Level::WARN, "supervisor task")) } + async fn shutdown(self) -> Result<(), Error> { + match self + .abortion_sender + .send(TerminationReport { + is_error: false, + has_expired: false, + origin: TerminationOrigin::Supervisor, + }) + .await + { + Ok(_) => self.task.await.map_err(|err| { + error!("couldn't finish supervisor task: {err}"); + Error::InternalSupervisorError + }), + Err(err) => { + error!("when sending termination report: {err}"); + self.task.abort(); + Err(Error::InternalSupervisorError) + } + } + } +} + +struct SyncServiceInner { + room_list_service: Arc, + encryption_sync_service: Arc, + state: SharedObservable, + encryption_sync_permit: Arc>, + /// Supervisor task ensuring proper termination. + /// + /// This task is waiting for a [`TerminationReport`] from any of the other + /// two tasks, or from a user request via [`SyncService::stop()`]. It + /// makes sure that the two services are properly shut up and just + /// interrupted. + /// + /// This is set at the same time as the other two tasks. + supervisor: Option, +} + +pub struct SyncService { + inner: Arc>, + + /// Room list service used to synchronize the rooms state. + room_list_service: Arc, + + /// What's the state of this sync service? This field is replicated from the + /// [`SyncServiceInner`] struct, but it should not be modified in this + /// struct. It's re-exposed here so we can subscribe to the state + /// without taking the lock on the `inner` field. + state: SharedObservable, + + /// Global lock to allow using at most one [`EncryptionSyncService`] at all + /// times. + /// + /// This ensures that there's only one ever existing in the application's + /// lifetime (under the assumption that there is at most one [`SyncService`] + /// per application). + encryption_sync_permit: Arc>, +} + +impl SyncService { + /// Create a new builder for configuring an `SyncService`. + pub fn builder(client: Client) -> SyncServiceBuilder { + SyncServiceBuilder::new(client) + } + + /// Get the underlying `RoomListService` instance for easier access to its + /// methods. + pub fn room_list_service(&self) -> Arc { + self.room_list_service.clone() + } + + /// Returns the state of the sync service. + pub fn state(&self) -> Subscriber { + self.state.subscribe() + } + async fn encryption_sync_task( encryption_sync: Arc, sender: Sender, @@ -318,13 +366,10 @@ impl SyncService { )); // Spawn the supervisor task. - inner.supervisor_sender = Some(sender); - inner.supervisor_task = Some(spawn(self.spawn_supervisor_task( - &inner, - room_list_task, - encryption_sync_task, - receiver, - ))); + inner.supervisor = Some( + SyncTaskSupervisor::new(&inner, room_list_task, encryption_sync_task, sender, receiver) + .await, + ); self.state.set(State::Running); } @@ -352,36 +397,14 @@ impl SyncService { // later, so that we're in a clean state independently of the request to // stop. - let sender = inner.supervisor_sender.clone(); - sender - .ok_or_else(|| { - error!("missing sender"); - Error::InternalSupervisorError - })? - .send(TerminationReport { - is_error: false, - has_expired: false, - origin: TerminationOrigin::Supervisor, - }) - .await - .map_err(|err| { - error!("when sending termination report: {err}"); - Error::InternalSupervisorError - })?; + // Remove the supervisor from our inner state and request the tasks to be + // shutdown. + let supervisor = inner.supervisor.take().ok_or_else(|| { + error!("The supervisor was not properly started up"); + Error::InternalSupervisorError + })?; - let supervisor_task = inner.supervisor_task.take(); - supervisor_task - .ok_or_else(|| { - error!("missing supervisor task"); - Error::InternalSupervisorError - })? - .await - .map_err(|err| { - error!("couldn't finish supervisor task: {err}"); - Error::InternalSupervisorError - })?; - - Ok(()) + supervisor.shutdown().await } /// Attempt to get a permit to use an `EncryptionSyncService` at a given @@ -413,7 +436,7 @@ struct TerminationReport { impl SyncService { /// Return the existential states of internal tasks. pub async fn is_supervisor_running(&self) -> bool { - self.inner.lock().await.supervisor_task.is_some() + self.inner.lock().await.supervisor.is_some() } } @@ -474,8 +497,7 @@ impl SyncServiceBuilder { encryption_sync_permit: encryption_sync_permit.clone(), inner: Arc::new(AsyncMutex::new(SyncServiceInner { - supervisor_task: None, - supervisor_sender: None, + supervisor: None, room_list_service, encryption_sync_service: encryption_sync, state, From 7c2b15fe864aef592cdd478594eb7251cdcda4a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 16 Jan 2025 14:38:37 +0100 Subject: [PATCH 40/55] refactor(ui): Move the spawning of the child tasks into the supervisor --- crates/matrix-sdk-ui/src/sync_service.rs | 54 ++++++++++++------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/crates/matrix-sdk-ui/src/sync_service.rs b/crates/matrix-sdk-ui/src/sync_service.rs index adfdef3f1..e1470e443 100644 --- a/crates/matrix-sdk-ui/src/sync_service.rs +++ b/crates/matrix-sdk-ui/src/sync_service.rs @@ -79,13 +79,11 @@ struct SyncTaskSupervisor { } impl SyncTaskSupervisor { - async fn new( - inner: &SyncServiceInner, - room_list_task: JoinHandle<()>, - encryption_sync_task: JoinHandle<()>, - sender: Sender, - receiver: Receiver, - ) -> Self { + async fn new(inner: &SyncServiceInner) -> Self { + let (sender, receiver) = tokio::sync::mpsc::channel(16); + let (room_list_task, encryption_sync_task) = + Self::spawn_child_tasks(inner, sender.clone()).await; + let task = spawn(Self::spawn_supervisor_task( inner, room_list_task, @@ -167,6 +165,27 @@ impl SyncTaskSupervisor { .instrument(tracing::span!(Level::WARN, "supervisor task")) } + async fn spawn_child_tasks( + inner: &SyncServiceInner, + sender: Sender, + ) -> (JoinHandle<()>, JoinHandle<()>) { + // First, take care of the room list. + let room_list_task = spawn(SyncService::room_list_sync_task( + inner.room_list_service.clone(), + sender.clone(), + )); + + // Then, take care of the encryption sync. + let sync_permit_guard = inner.encryption_sync_permit.clone().lock_owned().await; + let encryption_sync_task = spawn(SyncService::encryption_sync_task( + inner.encryption_sync_service.clone(), + sender.clone(), + sync_permit_guard, + )); + + (room_list_task, encryption_sync_task) + } + async fn shutdown(self) -> Result<(), Error> { match self .abortion_sender @@ -350,26 +369,7 @@ impl SyncService { } trace!("starting sync service"); - - let (sender, receiver) = tokio::sync::mpsc::channel(16); - - // First, take care of the room list. - let room_list_task = - spawn(Self::room_list_sync_task(self.room_list_service.clone(), sender.clone())); - - // Then, take care of the encryption sync. - let sync_permit_guard = inner.encryption_sync_permit.clone().lock_owned().await; - let encryption_sync_task = spawn(Self::encryption_sync_task( - inner.encryption_sync_service.clone(), - sender.clone(), - sync_permit_guard, - )); - - // Spawn the supervisor task. - inner.supervisor = Some( - SyncTaskSupervisor::new(&inner, room_list_task, encryption_sync_task, sender, receiver) - .await, - ); + inner.supervisor = Some(SyncTaskSupervisor::new(&inner).await); self.state.set(State::Running); } From 1b8a6b705c83a7f4235e41c8f0339d3bffd80c73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 16 Jan 2025 14:53:10 +0100 Subject: [PATCH 41/55] refactor(ui): Move the expiration of the sync services closer to the action --- crates/matrix-sdk-ui/src/sync_service.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/crates/matrix-sdk-ui/src/sync_service.rs b/crates/matrix-sdk-ui/src/sync_service.rs index e1470e443..d27390ed7 100644 --- a/crates/matrix-sdk-ui/src/sync_service.rs +++ b/crates/matrix-sdk-ui/src/sync_service.rs @@ -129,6 +129,10 @@ impl SyncTaskSupervisor { if let Err(err) = room_list_service.stop_sync() { warn!(?report, "unable to stop room list service: {err:#}"); } + + if report.has_expired { + room_list_service.expire_sync_session().await; + } } if let Err(err) = room_list_task.await { @@ -139,6 +143,10 @@ impl SyncTaskSupervisor { if let Err(err) = encryption_sync.stop_sync() { warn!(?report, "unable to stop encryption sync: {err:#}"); } + + if report.has_expired { + encryption_sync.expire_sync_session().await; + } } if let Err(err) = encryption_sync_task.await { @@ -146,15 +154,6 @@ impl SyncTaskSupervisor { } if report.is_error { - if report.has_expired { - if stop_room_list { - room_list_service.expire_sync_session().await; - } - if stop_encryption { - encryption_sync.expire_sync_session().await; - } - } - state.set(State::Error); } else if matches!(report.origin, TerminationOrigin::Supervisor) { state.set(State::Idle); From d14526f161eb1e52d29a22501384c5d0a8ade5b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 16 Jan 2025 15:25:53 +0100 Subject: [PATCH 42/55] refactor(ui): Move the task spawning functions under the supervisor --- crates/matrix-sdk-ui/src/sync_service.rs | 162 +++++++++++------------ 1 file changed, 80 insertions(+), 82 deletions(-) diff --git a/crates/matrix-sdk-ui/src/sync_service.rs b/crates/matrix-sdk-ui/src/sync_service.rs index d27390ed7..26fcfe5d8 100644 --- a/crates/matrix-sdk-ui/src/sync_service.rs +++ b/crates/matrix-sdk-ui/src/sync_service.rs @@ -169,14 +169,12 @@ impl SyncTaskSupervisor { sender: Sender, ) -> (JoinHandle<()>, JoinHandle<()>) { // First, take care of the room list. - let room_list_task = spawn(SyncService::room_list_sync_task( - inner.room_list_service.clone(), - sender.clone(), - )); + let room_list_task = + spawn(Self::room_list_sync_task(inner.room_list_service.clone(), sender.clone())); // Then, take care of the encryption sync. let sync_permit_guard = inner.encryption_sync_permit.clone().lock_owned().await; - let encryption_sync_task = spawn(SyncService::encryption_sync_task( + let encryption_sync_task = spawn(Self::encryption_sync_task( inner.encryption_sync_service.clone(), sender.clone(), sync_permit_guard, @@ -185,83 +183,6 @@ impl SyncTaskSupervisor { (room_list_task, encryption_sync_task) } - async fn shutdown(self) -> Result<(), Error> { - match self - .abortion_sender - .send(TerminationReport { - is_error: false, - has_expired: false, - origin: TerminationOrigin::Supervisor, - }) - .await - { - Ok(_) => self.task.await.map_err(|err| { - error!("couldn't finish supervisor task: {err}"); - Error::InternalSupervisorError - }), - Err(err) => { - error!("when sending termination report: {err}"); - self.task.abort(); - Err(Error::InternalSupervisorError) - } - } - } -} - -struct SyncServiceInner { - room_list_service: Arc, - encryption_sync_service: Arc, - state: SharedObservable, - encryption_sync_permit: Arc>, - /// Supervisor task ensuring proper termination. - /// - /// This task is waiting for a [`TerminationReport`] from any of the other - /// two tasks, or from a user request via [`SyncService::stop()`]. It - /// makes sure that the two services are properly shut up and just - /// interrupted. - /// - /// This is set at the same time as the other two tasks. - supervisor: Option, -} - -pub struct SyncService { - inner: Arc>, - - /// Room list service used to synchronize the rooms state. - room_list_service: Arc, - - /// What's the state of this sync service? This field is replicated from the - /// [`SyncServiceInner`] struct, but it should not be modified in this - /// struct. It's re-exposed here so we can subscribe to the state - /// without taking the lock on the `inner` field. - state: SharedObservable, - - /// Global lock to allow using at most one [`EncryptionSyncService`] at all - /// times. - /// - /// This ensures that there's only one ever existing in the application's - /// lifetime (under the assumption that there is at most one [`SyncService`] - /// per application). - encryption_sync_permit: Arc>, -} - -impl SyncService { - /// Create a new builder for configuring an `SyncService`. - pub fn builder(client: Client) -> SyncServiceBuilder { - SyncServiceBuilder::new(client) - } - - /// Get the underlying `RoomListService` instance for easier access to its - /// methods. - pub fn room_list_service(&self) -> Arc { - self.room_list_service.clone() - } - - /// Returns the state of the sync service. - pub fn state(&self) -> Subscriber { - self.state.subscribe() - } - async fn encryption_sync_task( encryption_sync: Arc, sender: Sender, @@ -352,6 +273,83 @@ impl SyncService { } } + async fn shutdown(self) -> Result<(), Error> { + match self + .abortion_sender + .send(TerminationReport { + is_error: false, + has_expired: false, + origin: TerminationOrigin::Supervisor, + }) + .await + { + Ok(_) => self.task.await.map_err(|err| { + error!("couldn't finish supervisor task: {err}"); + Error::InternalSupervisorError + }), + Err(err) => { + error!("when sending termination report: {err}"); + self.task.abort(); + Err(Error::InternalSupervisorError) + } + } + } +} + +struct SyncServiceInner { + room_list_service: Arc, + encryption_sync_service: Arc, + state: SharedObservable, + encryption_sync_permit: Arc>, + /// Supervisor task ensuring proper termination. + /// + /// This task is waiting for a [`TerminationReport`] from any of the other + /// two tasks, or from a user request via [`SyncService::stop()`]. It + /// makes sure that the two services are properly shut up and just + /// interrupted. + /// + /// This is set at the same time as the other two tasks. + supervisor: Option, +} + +pub struct SyncService { + inner: Arc>, + + /// Room list service used to synchronize the rooms state. + room_list_service: Arc, + + /// What's the state of this sync service? This field is replicated from the + /// [`SyncServiceInner`] struct, but it should not be modified in this + /// struct. It's re-exposed here so we can subscribe to the state + /// without taking the lock on the `inner` field. + state: SharedObservable, + + /// Global lock to allow using at most one [`EncryptionSyncService`] at all + /// times. + /// + /// This ensures that there's only one ever existing in the application's + /// lifetime (under the assumption that there is at most one [`SyncService`] + /// per application). + encryption_sync_permit: Arc>, +} + +impl SyncService { + /// Create a new builder for configuring an `SyncService`. + pub fn builder(client: Client) -> SyncServiceBuilder { + SyncServiceBuilder::new(client) + } + + /// Get the underlying `RoomListService` instance for easier access to its + /// methods. + pub fn room_list_service(&self) -> Arc { + self.room_list_service.clone() + } + + /// Returns the state of the sync service. + pub fn state(&self) -> Subscriber { + self.state.subscribe() + } + /// Start (or restart) the underlying sliding syncs. /// /// This can be called multiple times safely: From b52cf8327a1d1c74fb56e95639ff324a5e1425cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 16 Jan 2025 16:27:26 +0100 Subject: [PATCH 43/55] refactor(ui): Remove some early returns from the sync service Now that the various match branches in the start and stop method of the SyncService are minimized we can remove the early returns. This should allow us to more easily add new branches. --- crates/matrix-sdk-ui/src/sync_service.rs | 48 ++++++++++++------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/crates/matrix-sdk-ui/src/sync_service.rs b/crates/matrix-sdk-ui/src/sync_service.rs index 26fcfe5d8..e2daf5cf2 100644 --- a/crates/matrix-sdk-ui/src/sync_service.rs +++ b/crates/matrix-sdk-ui/src/sync_service.rs @@ -360,15 +360,16 @@ impl SyncService { let mut inner = self.inner.lock().await; // Only (re)start the tasks if any was stopped. - if matches!(self.state.get(), State::Running) { - // It was already true, so we can skip the restart. - return; + match inner.state.get() { + // If we're already running, there's nothing to do. + State::Running => (), + State::Idle | State::Terminated | State::Error => { + trace!("starting sync service"); + + inner.supervisor = Some(SyncTaskSupervisor::new(&inner).await); + inner.state.set(State::Running); + } } - - trace!("starting sync service"); - inner.supervisor = Some(SyncTaskSupervisor::new(&inner).await); - - self.state.set(State::Running); } /// Stop the underlying sliding syncs. @@ -380,28 +381,27 @@ impl SyncService { pub async fn stop(&self) -> Result<(), Error> { let mut inner = self.inner.lock().await; - match self.state.get() { + match inner.state.get() { State::Idle | State::Terminated | State::Error => { // No need to stop if we were not running. - return Ok(()); + Ok(()) } - State::Running => {} - }; + State::Running => { + trace!("pausing sync service"); - trace!("pausing sync service"); + // First, request to stop the two underlying syncs; we'll look at the results + // later, so that we're in a clean state independently of the request to stop. - // First, request to stop the two underlying syncs; we'll look at the results - // later, so that we're in a clean state independently of the request to - // stop. + // Remove the supervisor from our inner state and request the tasks to be + // shutdown. + let supervisor = inner.supervisor.take().ok_or_else(|| { + error!("The supervisor was not properly started up"); + Error::InternalSupervisorError + })?; - // Remove the supervisor from our inner state and request the tasks to be - // shutdown. - let supervisor = inner.supervisor.take().ok_or_else(|| { - error!("The supervisor was not properly started up"); - Error::InternalSupervisorError - })?; - - supervisor.shutdown().await + supervisor.shutdown().await + } + } } /// Attempt to get a permit to use an `EncryptionSyncService` at a given From 842d32d41b50dda6fd62ee4c8d12e73a9702d18a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 16 Jan 2025 16:29:08 +0100 Subject: [PATCH 44/55] refactor(ui): Prettify the two sync tasks a bit --- crates/matrix-sdk-ui/src/sync_service.rs | 29 +++++++++++++++--------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/crates/matrix-sdk-ui/src/sync_service.rs b/crates/matrix-sdk-ui/src/sync_service.rs index e2daf5cf2..6ff148ea3 100644 --- a/crates/matrix-sdk-ui/src/sync_service.rs +++ b/crates/matrix-sdk-ui/src/sync_service.rs @@ -183,33 +183,38 @@ impl SyncTaskSupervisor { (room_list_task, encryption_sync_task) } + fn check_if_expired(err: &matrix_sdk::Error) -> bool { + err.client_api_error_kind() == Some(&ruma::api::client::error::ErrorKind::UnknownPos) + } + async fn encryption_sync_task( encryption_sync: Arc, sender: Sender, sync_permit_guard: OwnedMutexGuard, ) { + use encryption_sync_service::Error; + let encryption_sync_stream = encryption_sync.sync(sync_permit_guard); pin_mut!(encryption_sync_stream); let (is_error, has_expired) = loop { - let res = encryption_sync_stream.next().await; - match res { + match encryption_sync_stream.next().await { Some(Ok(())) => { // Carry on. } Some(Err(err)) => { // If the encryption sync error was an expired session, also expire the // room list sync. - let has_expired = if let encryption_sync_service::Error::SlidingSync(err) = &err - { - err.client_api_error_kind() - == Some(&ruma::api::client::error::ErrorKind::UnknownPos) + let has_expired = if let Error::SlidingSync(err) = &err { + Self::check_if_expired(err) } else { false }; + if !has_expired { error!("Error while processing encryption in sync service: {err:#}"); } + break (true, has_expired); } None => { @@ -235,27 +240,29 @@ impl SyncTaskSupervisor { room_list_service: Arc, sender: Sender, ) { + use room_list_service::Error; + let room_list_stream = room_list_service.sync(); pin_mut!(room_list_stream); let (is_error, has_expired) = loop { - let res = room_list_stream.next().await; - match res { + match room_list_stream.next().await { Some(Ok(())) => { // Carry on. } Some(Err(err)) => { // If the room list error was an expired session, also expire the // encryption sync. - let has_expired = if let room_list_service::Error::SlidingSync(err) = &err { - err.client_api_error_kind() - == Some(&ruma::api::client::error::ErrorKind::UnknownPos) + let has_expired = if let Error::SlidingSync(err) = &err { + Self::check_if_expired(err) } else { false }; + if !has_expired { error!("Error while processing room list in sync service: {err:#}"); } + break (true, has_expired); } None => { From 28fb6f7c2770908158a1f1d29b9aac2850b17e84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 16 Jan 2025 16:41:37 +0100 Subject: [PATCH 45/55] fix(ui): Shutdown the child tasks if the channel got closed in the supervisor --- crates/matrix-sdk-ui/src/sync_service.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/crates/matrix-sdk-ui/src/sync_service.rs b/crates/matrix-sdk-ui/src/sync_service.rs index 6ff148ea3..0fc6904d5 100644 --- a/crates/matrix-sdk-ui/src/sync_service.rs +++ b/crates/matrix-sdk-ui/src/sync_service.rs @@ -109,9 +109,13 @@ impl SyncTaskSupervisor { let state = inner.state.clone(); async move { - let Some(report) = receiver.recv().await else { + let report = if let Some(report) = receiver.recv().await { + report + } else { info!("internal channel has been closed?"); - return; + // We should still stop the child tasks in the unlikely scenario that our + // receiver died. + TerminationReport::supervisor_error() }; // If one service failed, make sure to request stopping the other one. @@ -435,6 +439,16 @@ struct TerminationReport { origin: TerminationOrigin, } +impl TerminationReport { + fn supervisor_error() -> Self { + TerminationReport { + is_error: true, + has_expired: false, + origin: TerminationOrigin::Supervisor, + } + } +} + // Testing helpers, mostly. #[doc(hidden)] impl SyncService { From 06ad67f99c0ae034c077841f3a9cb9f3848aa79a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 16 Jan 2025 17:14:51 +0100 Subject: [PATCH 46/55] docs(ui): Polish the documentation of the SyncService a bit --- Cargo.lock | 1 + crates/matrix-sdk-ui/Cargo.toml | 1 + crates/matrix-sdk-ui/src/sync_service.rs | 82 ++++++++++++++++++------ 3 files changed, 64 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 071342235..1cc14a8d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3543,6 +3543,7 @@ dependencies = [ "unicode-normalization", "unicode-segmentation", "uniffi", + "url", "wiremock", ] diff --git a/crates/matrix-sdk-ui/Cargo.toml b/crates/matrix-sdk-ui/Cargo.toml index 62769b597..2b3e1eb88 100644 --- a/crates/matrix-sdk-ui/Cargo.toml +++ b/crates/matrix-sdk-ui/Cargo.toml @@ -64,6 +64,7 @@ matrix-sdk = { workspace = true, features = ["testing", "sqlite"] } matrix-sdk-test = { workspace = true } stream_assert = { workspace = true } tempfile = { workspace = true } +url = { workspace = true } wiremock = { workspace = true } [lints] diff --git a/crates/matrix-sdk-ui/src/sync_service.rs b/crates/matrix-sdk-ui/src/sync_service.rs index 0fc6904d5..4aa70fbbf 100644 --- a/crates/matrix-sdk-ui/src/sync_service.rs +++ b/crates/matrix-sdk-ui/src/sync_service.rs @@ -18,10 +18,9 @@ //! This is an opiniated way to run both APIs, with high-level callbacks that //! should be called in reaction to user actions and/or system events. //! -//! The sync service will signal errors via its -//! [`state`](SyncService::state) that the user -//! MUST observe. Whenever an error/termination is observed, the user MUST call -//! [`SyncService::start()`] again to restart the room list sync. +//! The sync service will signal errors via its [`state`](SyncService::state) +//! that the user MUST observe. Whenever an error/termination is observed, the +//! user MUST call [`SyncService::start()`] again to restart the room list sync. use std::sync::Arc; @@ -47,9 +46,9 @@ use crate::{ /// Current state of the application. /// /// This is a high-level state indicating what's the status of the underlying -/// syncs. The application starts in `Running` mode, and then hits a terminal -/// state `Terminated` (if it gracefully exited) or `Error` (in case any of the -/// underlying syncs ran into an error). +/// syncs. The application starts in [`State::Running`] mode, and then hits a +/// terminal state [`State::Terminated`] (if it gracefully exited) or +/// [`State::Error`] (in case any of the underlying syncs ran into an error). /// /// It is the responsibility of the caller to restart the application using the /// [`SyncService::start`] method, in case it terminated, gracefully or not. @@ -72,9 +71,8 @@ pub enum State { struct SyncTaskSupervisor { /// The task that supervises the two sync tasks. task: JoinHandle<()>, - /// `TerminationReport` sender for the [`Self::stop()`] function. - /// - /// This is set at the same time as all the tasks in [`Self::start()`]. + /// [`TerminationReport`] sender for the [`SyncTaskSupervisor::shutdown()`] + /// function. abortion_sender: Sender, } @@ -95,9 +93,9 @@ impl SyncTaskSupervisor { } /// The role of the supervisor task is to wait for a termination message - /// (`TerminationReport`), sent either because we wanted to stop both - /// syncs, or because one of the syncs failed (in which case we'll stop - /// the other one too). + /// ([`TerminationReport`]), sent either because we wanted to stop both + /// syncs, or because one of the syncs failed (in which case we'll stop the + /// other one too). fn spawn_supervisor_task( inner: &SyncServiceInner, room_list_task: JoinHandle<()>, @@ -126,8 +124,8 @@ impl SyncTaskSupervisor { }; // Stop both services, and wait for the streams to properly finish: at some - // point they'll return `None` and will exit their infinite loops, - // and their tasks will gracefully terminate. + // point they'll return `None` and will exit their infinite loops, and their + // tasks will gracefully terminate. if stop_room_list { if let Err(err) = room_list_service.stop_sync() { @@ -300,6 +298,8 @@ impl SyncTaskSupervisor { }), Err(err) => { error!("when sending termination report: {err}"); + // Let's abort the task if it won't shut down properly, otherwise we would have + // left it as a detached task. self.task.abort(); Err(Error::InternalSupervisorError) } @@ -323,6 +323,47 @@ struct SyncServiceInner { supervisor: Option, } +/// A high level manager for your Matrix syncing needs. +/// +/// The [`SyncService`] is responsible for managing real-time synchronization +/// with a Matrix server. It can initiate and maintain the necessary +/// synchronization tasks for you. +/// +/// # Example +/// +/// ```no_run +/// use matrix_sdk::Client; +/// use matrix_sdk_ui::sync_service::{State, SyncService}; +/// # use url::Url; +/// # async { +/// let homeserver = Url::parse("http://example.com")?; +/// let client = Client::new(homeserver).await?; +/// +/// client +/// .matrix_auth() +/// .login_username("example", "wordpass") +/// .initial_device_display_name("My bot") +/// .await?; +/// +/// let sync_service = SyncService::builder(client).build().await?; +/// let mut state = sync_service.state(); +/// +/// while let Some(state) = state.next().await { +/// match state { +/// State::Idle => eprintln!("The sync service is idle."), +/// State::Running => eprintln!("The sync has started to run."), +/// State::Terminated => { +/// eprintln!("The sync service has been gracefully terminated"); +/// break; +/// } +/// State::Error => { +/// eprintln!("The sync service has run into an error"); +/// break; +/// } +/// } +/// } +/// # anyhow::Ok(()) }; +/// ``` pub struct SyncService { inner: Arc>, @@ -331,8 +372,8 @@ pub struct SyncService { /// What's the state of this sync service? This field is replicated from the /// [`SyncServiceInner`] struct, but it should not be modified in this - /// struct. It's re-exposed here so we can subscribe to the state - /// without taking the lock on the `inner` field. + /// struct. It's re-exposed here so we can subscribe to the state without + /// taking the lock on the `inner` field. state: SharedObservable, /// Global lock to allow using at most one [`EncryptionSyncService`] at all @@ -487,11 +528,11 @@ impl SyncServiceBuilder { self } - /// Finish setting up the `SyncService`. + /// Finish setting up the [`SyncService`]. /// /// This creates the underlying sliding syncs, and will *not* start them in - /// the background. The resulting `SyncService` must be kept alive as - /// long as the sliding syncs are supposed to run. + /// the background. The resulting [`SyncService`] must be kept alive as long + /// as the sliding syncs are supposed to run. pub async fn build(self) -> Result { let encryption_sync_permit = Arc::new(AsyncMutex::new(EncryptionSyncPermit::new())); @@ -536,6 +577,7 @@ pub enum Error { #[error(transparent)] EncryptionSync(#[from] encryption_sync_service::Error), + /// An error had occurred in the sync task supervisor, likely due to a bug. #[error("the supervisor channel has run into an unexpected error")] InternalSupervisorError, } From be71c6df56f46a540a332a60c31ffe33d0bb9ae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 23 Jan 2025 10:40:20 +0100 Subject: [PATCH 47/55] docs: Document that the SyncService requires MSC4186 --- crates/matrix-sdk-ui/src/sync_service.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/matrix-sdk-ui/src/sync_service.rs b/crates/matrix-sdk-ui/src/sync_service.rs index 4aa70fbbf..4c7d6a29b 100644 --- a/crates/matrix-sdk-ui/src/sync_service.rs +++ b/crates/matrix-sdk-ui/src/sync_service.rs @@ -329,6 +329,11 @@ struct SyncServiceInner { /// with a Matrix server. It can initiate and maintain the necessary /// synchronization tasks for you. /// +/// **Note**: The [`SyncService`] requires a server with support for [MSC4186], +/// otherwise it will fail with an 404 `M_UNRECOGNIZED` request error. +/// +/// [MSC4186]: https://github.com/matrix-org/matrix-spec-proposals/pull/4186/ +/// /// # Example /// /// ```no_run From 30d7fac92720571ae752de7dc64951a322ac26be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 23 Jan 2025 12:44:28 +0100 Subject: [PATCH 48/55] refactor(ui): Remove some unneeded references from the SyncServiceInner --- crates/matrix-sdk-ui/src/sync_service.rs | 43 +++++++++++++++--------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/crates/matrix-sdk-ui/src/sync_service.rs b/crates/matrix-sdk-ui/src/sync_service.rs index 4c7d6a29b..e54c3eee7 100644 --- a/crates/matrix-sdk-ui/src/sync_service.rs +++ b/crates/matrix-sdk-ui/src/sync_service.rs @@ -77,13 +77,23 @@ struct SyncTaskSupervisor { } impl SyncTaskSupervisor { - async fn new(inner: &SyncServiceInner) -> Self { + async fn new( + inner: &SyncServiceInner, + room_list_service: Arc, + encryption_sync_permit: Arc>, + ) -> Self { let (sender, receiver) = tokio::sync::mpsc::channel(16); - let (room_list_task, encryption_sync_task) = - Self::spawn_child_tasks(inner, sender.clone()).await; + let (room_list_task, encryption_sync_task) = Self::spawn_child_tasks( + inner, + room_list_service.clone(), + encryption_sync_permit, + sender.clone(), + ) + .await; let task = spawn(Self::spawn_supervisor_task( inner, + room_list_service, room_list_task, encryption_sync_task, receiver, @@ -98,12 +108,12 @@ impl SyncTaskSupervisor { /// other one too). fn spawn_supervisor_task( inner: &SyncServiceInner, + room_list_service: Arc, room_list_task: JoinHandle<()>, encryption_sync_task: JoinHandle<()>, mut receiver: Receiver, ) -> impl Future { let encryption_sync = inner.encryption_sync_service.clone(); - let room_list_service = inner.room_list_service.clone(); let state = inner.state.clone(); async move { @@ -168,14 +178,15 @@ impl SyncTaskSupervisor { async fn spawn_child_tasks( inner: &SyncServiceInner, + room_list_service: Arc, + encryption_sync_permit: Arc>, sender: Sender, ) -> (JoinHandle<()>, JoinHandle<()>) { // First, take care of the room list. - let room_list_task = - spawn(Self::room_list_sync_task(inner.room_list_service.clone(), sender.clone())); + let room_list_task = spawn(Self::room_list_sync_task(room_list_service, sender.clone())); // Then, take care of the encryption sync. - let sync_permit_guard = inner.encryption_sync_permit.clone().lock_owned().await; + let sync_permit_guard = encryption_sync_permit.clone().lock_owned().await; let encryption_sync_task = spawn(Self::encryption_sync_task( inner.encryption_sync_service.clone(), sender.clone(), @@ -308,10 +319,8 @@ impl SyncTaskSupervisor { } struct SyncServiceInner { - room_list_service: Arc, encryption_sync_service: Arc, state: SharedObservable, - encryption_sync_permit: Arc>, /// Supervisor task ensuring proper termination. /// /// This task is waiting for a [`TerminationReport`] from any of the other @@ -423,7 +432,14 @@ impl SyncService { State::Idle | State::Terminated | State::Error => { trace!("starting sync service"); - inner.supervisor = Some(SyncTaskSupervisor::new(&inner).await); + inner.supervisor = Some( + SyncTaskSupervisor::new( + &inner, + self.room_list_service.clone(), + self.encryption_sync_permit.clone(), + ) + .await, + ); inner.state.set(State::Running); } } @@ -557,15 +573,12 @@ impl SyncServiceBuilder { Ok(SyncService { state: state.clone(), - room_list_service: room_list_service.clone(), - encryption_sync_permit: encryption_sync_permit.clone(), - + room_list_service, + encryption_sync_permit, inner: Arc::new(AsyncMutex::new(SyncServiceInner { supervisor: None, - room_list_service, encryption_sync_service: encryption_sync, state, - encryption_sync_permit, })), }) } From 8a4918309a9226d7eac7afa3386204e13e736bbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 23 Jan 2025 13:37:27 +0100 Subject: [PATCH 49/55] refactor(ui): Rename the abortion sender in the SyncService Termination aligns better with the existing terminology. --- crates/matrix-sdk-ui/src/sync_service.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/matrix-sdk-ui/src/sync_service.rs b/crates/matrix-sdk-ui/src/sync_service.rs index e54c3eee7..1f17bd3ff 100644 --- a/crates/matrix-sdk-ui/src/sync_service.rs +++ b/crates/matrix-sdk-ui/src/sync_service.rs @@ -73,7 +73,7 @@ struct SyncTaskSupervisor { task: JoinHandle<()>, /// [`TerminationReport`] sender for the [`SyncTaskSupervisor::shutdown()`] /// function. - abortion_sender: Sender, + termination_sender: Sender, } impl SyncTaskSupervisor { @@ -99,7 +99,7 @@ impl SyncTaskSupervisor { receiver, )); - Self { task, abortion_sender: sender } + Self { task, termination_sender: sender } } /// The role of the supervisor task is to wait for a termination message @@ -295,7 +295,7 @@ impl SyncTaskSupervisor { async fn shutdown(self) -> Result<(), Error> { match self - .abortion_sender + .termination_sender .send(TerminationReport { is_error: false, has_expired: false, From 7cc121ab3823257485813a7ab58a455e18995e83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 23 Jan 2025 15:00:23 +0100 Subject: [PATCH 50/55] docs(ui): Clarify that the supervisor encapsulates the child tasks in its own task --- crates/matrix-sdk-ui/src/sync_service.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/matrix-sdk-ui/src/sync_service.rs b/crates/matrix-sdk-ui/src/sync_service.rs index 1f17bd3ff..b5ab12c44 100644 --- a/crates/matrix-sdk-ui/src/sync_service.rs +++ b/crates/matrix-sdk-ui/src/sync_service.rs @@ -66,10 +66,16 @@ pub enum State { Error, } -/// A supervisor that starts two sync tasks, one for the room list and one for -/// the end-to-end encryption support. +/// A supervisor responsible for managing two sync tasks: one for handling the +/// room list and another for supporting end-to-end encryption. +/// +/// The two sync tasks are spawned as child tasks and are contained within t +/// supervising task, which is stored in the [`SyncTaskSupervisor::task`] field. +/// +/// The supervisor ensures the two child tasks are managed as a single unit, +/// allowing for them to be shutdown in unison. struct SyncTaskSupervisor { - /// The task that supervises the two sync tasks. + /// The supervising task that manages and contains the two sync child tasks. task: JoinHandle<()>, /// [`TerminationReport`] sender for the [`SyncTaskSupervisor::shutdown()`] /// function. From f8ec957193923944fc53be9f357b9d2d9cba080e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 23 Jan 2025 17:15:17 +0100 Subject: [PATCH 51/55] doc(ui): Reword the doc comment for the is_supervisor_task_running method --- crates/matrix-sdk-ui/src/sync_service.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/matrix-sdk-ui/src/sync_service.rs b/crates/matrix-sdk-ui/src/sync_service.rs index b5ab12c44..c8bd24c52 100644 --- a/crates/matrix-sdk-ui/src/sync_service.rs +++ b/crates/matrix-sdk-ui/src/sync_service.rs @@ -520,7 +520,7 @@ impl TerminationReport { // Testing helpers, mostly. #[doc(hidden)] impl SyncService { - /// Return the existential states of internal tasks. + /// Is the task supervisor running? pub async fn is_supervisor_running(&self) -> bool { self.inner.lock().await.supervisor.is_some() } From 3f398d8934b1a9d5f8e0917a52ec06bb483908c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 24 Jan 2025 17:00:00 +0100 Subject: [PATCH 52/55] chore(ui): Move the SyncService stop logic out of the State::Running branch --- crates/matrix-sdk-ui/src/sync_service.rs | 32 ++++++++++++------------ 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/crates/matrix-sdk-ui/src/sync_service.rs b/crates/matrix-sdk-ui/src/sync_service.rs index c8bd24c52..e566edc57 100644 --- a/crates/matrix-sdk-ui/src/sync_service.rs +++ b/crates/matrix-sdk-ui/src/sync_service.rs @@ -463,24 +463,24 @@ impl SyncService { match inner.state.get() { State::Idle | State::Terminated | State::Error => { // No need to stop if we were not running. - Ok(()) - } - State::Running => { - trace!("pausing sync service"); - - // First, request to stop the two underlying syncs; we'll look at the results - // later, so that we're in a clean state independently of the request to stop. - - // Remove the supervisor from our inner state and request the tasks to be - // shutdown. - let supervisor = inner.supervisor.take().ok_or_else(|| { - error!("The supervisor was not properly started up"); - Error::InternalSupervisorError - })?; - - supervisor.shutdown().await + return Ok(()); } + State::Running => (), } + + trace!("pausing sync service"); + + // First, request to stop the two underlying syncs; we'll look at the results + // later, so that we're in a clean state independently of the request to stop. + + // Remove the supervisor from our inner state and request the tasks to be + // shutdown. + let supervisor = inner.supervisor.take().ok_or_else(|| { + error!("The supervisor was not properly started up"); + Error::InternalSupervisorError + })?; + + supervisor.shutdown().await } /// Attempt to get a permit to use an `EncryptionSyncService` at a given From 8de15429fb2e31abdbf745eefb7c233888918e2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 28 Jan 2025 09:35:26 +0100 Subject: [PATCH 53/55] chore: Fix a typo --- crates/matrix-sdk-ui/src/sync_service.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/matrix-sdk-ui/src/sync_service.rs b/crates/matrix-sdk-ui/src/sync_service.rs index e566edc57..546031938 100644 --- a/crates/matrix-sdk-ui/src/sync_service.rs +++ b/crates/matrix-sdk-ui/src/sync_service.rs @@ -69,7 +69,7 @@ pub enum State { /// A supervisor responsible for managing two sync tasks: one for handling the /// room list and another for supporting end-to-end encryption. /// -/// The two sync tasks are spawned as child tasks and are contained within t +/// The two sync tasks are spawned as child tasks and are contained within the /// supervising task, which is stored in the [`SyncTaskSupervisor::task`] field. /// /// The supervisor ensures the two child tasks are managed as a single unit, From d8ba2b521c275c142dabd97a6d980cce5b506a3b Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Mon, 20 Jan 2025 13:47:03 +0000 Subject: [PATCH 54/55] refactor(crypto): extract a method from a test that I will re-use later --- .../src/machine/tests/mod.rs | 74 ++++++++++--------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/machine/tests/mod.rs b/crates/matrix-sdk-crypto/src/machine/tests/mod.rs index 872650118..b73994dcc 100644 --- a/crates/matrix-sdk-crypto/src/machine/tests/mod.rs +++ b/crates/matrix-sdk-crypto/src/machine/tests/mod.rs @@ -38,7 +38,7 @@ use ruma::{ room_id, serde::Raw, uint, user_id, DeviceId, DeviceKeyAlgorithm, DeviceKeyId, MilliSecondsSinceUnixEpoch, - OneTimeKeyAlgorithm, TransactionId, UserId, + OneTimeKeyAlgorithm, RoomId, TransactionId, UserId, }; use serde_json::json; use vodozemac::{ @@ -48,7 +48,7 @@ use vodozemac::{ use super::CrossSigningBootstrapRequests; use crate::{ - error::EventError, + error::{EventError, OlmResult}, machine::{ test_helpers::{ get_machine_after_query_test_helper, get_machine_pair_with_session, @@ -58,7 +58,7 @@ use crate::{ }, olm::{BackedUpRoomKey, ExportedRoomKey, SenderData, VerifyJson}, session_manager::CollectStrategy, - store::{BackupDecryptionKey, Changes, CryptoStore, MemoryStore}, + store::{BackupDecryptionKey, Changes, CryptoStore, MemoryStore, RoomKeyInfo}, types::{ events::{ room::encrypted::{EncryptedToDeviceEvent, ToDeviceEncryptedEventContent}, @@ -388,9 +388,37 @@ async fn test_missing_sessions_calculation() { #[async_test] async fn test_room_key_sharing() { let (alice, bob) = get_machine_pair_with_session(alice_id(), user_id(), false).await; - let room_id = room_id!("!test:example.org"); + let (decrypted, room_key_updates) = + send_room_key_to_device(&alice, &bob, room_id).await.unwrap(); + + let event = decrypted[0].deserialize().unwrap(); + + if let AnyToDeviceEvent::RoomKey(event) = event { + assert_eq!(&event.sender, alice.user_id()); + assert!(event.content.session_key.is_empty()); + } else { + panic!("expected RoomKeyEvent found {event:?}"); + } + + let alice_session = + alice.inner.group_session_manager.get_outbound_group_session(room_id).unwrap(); + + let session = bob.store().get_inbound_group_session(room_id, alice_session.session_id()).await; + + assert!(session.unwrap().is_some()); + + assert_eq!(room_key_updates.len(), 1); + assert_eq!(room_key_updates[0].room_id, room_id); + assert_eq!(room_key_updates[0].session_id, alice_session.session_id()); +} + +async fn send_room_key_to_device( + alice: &OlmMachine, + bob: &OlmMachine, + room_id: &RoomId, +) -> OlmResult<(Vec>, Vec)> { let to_device_requests = alice .share_room_key(room_id, iter::once(bob.user_id()), EncryptionSettings::default()) .await @@ -402,36 +430,14 @@ async fn test_room_key_sharing() { ); let event = json_convert(&event).unwrap(); - let alice_session = - alice.inner.group_session_manager.get_outbound_group_session(room_id).unwrap(); - - let (decrypted, room_key_updates) = bob - .receive_sync_changes(EncryptionSyncChanges { - to_device_events: vec![event], - changed_devices: &Default::default(), - one_time_keys_counts: &Default::default(), - unused_fallback_keys: None, - next_batch_token: None, - }) - .await - .unwrap(); - - let event = decrypted[0].deserialize().unwrap(); - - if let AnyToDeviceEvent::RoomKey(event) = event { - assert_eq!(&event.sender, alice.user_id()); - assert!(event.content.session_key.is_empty()); - } else { - panic!("expected RoomKeyEvent found {event:?}"); - } - - let session = bob.store().get_inbound_group_session(room_id, alice_session.session_id()).await; - - assert!(session.unwrap().is_some()); - - assert_eq!(room_key_updates.len(), 1); - assert_eq!(room_key_updates[0].room_id, room_id); - assert_eq!(room_key_updates[0].session_id, alice_session.session_id()); + bob.receive_sync_changes(EncryptionSyncChanges { + to_device_events: vec![event], + changed_devices: &Default::default(), + one_time_keys_counts: &Default::default(), + unused_fallback_keys: None, + next_batch_token: None, + }) + .await } #[async_test] From 447bd67fe1f8c0fb8e41244adbe8b73c36580f63 Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Thu, 16 Jan 2025 11:28:43 +0000 Subject: [PATCH 55/55] feat(crypto): Ignore to-device messages from dehydrated devices --- crates/matrix-sdk-crypto/src/machine/mod.rs | 93 ++++++++++++++-- .../src/machine/test_helpers.rs | 61 ++++++++++- .../src/machine/tests/mod.rs | 103 +++++++++++++++--- 3 files changed, 229 insertions(+), 28 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/machine/mod.rs b/crates/matrix-sdk-crypto/src/machine/mod.rs index fc79eea16..c33f169ab 100644 --- a/crates/matrix-sdk-crypto/src/machine/mod.rs +++ b/crates/matrix-sdk-crypto/src/machine/mod.rs @@ -852,9 +852,14 @@ impl OlmMachine { let mut decrypted = transaction.account().await?.decrypt_to_device_event(&self.inner.store, event).await?; - // Handle the decrypted event, e.g. fetch out Megolm sessions out of - // the event. - self.handle_decrypted_to_device_event(transaction.cache(), &mut decrypted, changes).await?; + // We ignore all to-device events from dehydrated devices - we should not + // receive any + if !self.to_device_event_is_from_dehydrated_device(&decrypted, &event.sender).await? { + // Handle the decrypted event, e.g. fetch out Megolm sessions out of + // the event. + self.handle_decrypted_to_device_event(transaction.cache(), &mut decrypted, changes) + .await?; + } Ok(decrypted) } @@ -1259,13 +1264,20 @@ impl OlmMachine { } } + /// Decrypt the supplied to-device event (if needed, and if we can) and + /// handle it. + /// + /// Return the same event, decrypted if possible and needed. + /// + /// If we can identify that this to-device event came from a dehydrated + /// device, this method does not process it, and returns `None`. #[instrument(skip_all, fields(sender, event_type, message_id))] async fn receive_to_device_event( &self, transaction: &mut StoreTransaction, changes: &mut Changes, mut raw_event: Raw, - ) -> Raw { + ) -> Option> { Self::record_message_id(&raw_event); let event: ToDeviceEvents = match raw_event.deserialize_as() { @@ -1274,7 +1286,7 @@ impl OlmMachine { // Skip invalid events. warn!("Received an invalid to-device event: {e}"); - return raw_event; + return Some(raw_event); } }; @@ -1299,10 +1311,30 @@ impl OlmMachine { } } - return raw_event; + return Some(raw_event); } }; + // We ignore all to-device events from dehydrated devices - we should not + // receive any + match self.to_device_event_is_from_dehydrated_device(&decrypted, &e.sender).await { + Ok(true) => { + warn!( + sender = ?e.sender, + session = ?decrypted.session, + "Received a to-device event from a dehydrated device. This is unexpected: ignoring event" + ); + return None; + } + Ok(false) => {} + Err(err) => { + error!( + error = ?err, + "Couldn't check whether event is from dehydrated device", + ); + } + } + // New sessions modify the account so we need to save that // one as well. match decrypted.session { @@ -1336,7 +1368,41 @@ impl OlmMachine { e => self.handle_to_device_event(changes, &e).await, } - raw_event + Some(raw_event) + } + + /// Decide whether a decrypted to-device event was sent from a dehydrated + /// device. + /// + /// This accepts an [`OlmDecryptionInfo`] because it deals with a decrypted + /// event. + async fn to_device_event_is_from_dehydrated_device( + &self, + decrypted: &OlmDecryptionInfo, + sender_user_id: &UserId, + ) -> OlmResult { + // Does the to-device message include device info? + if let Some(device_keys) = decrypted.result.event.sender_device_keys() { + // There is no need to check whether the device keys are signed correctly - any + // to-device message that claims to be from a dehydrated device is weird, so we + // will drop it. + + // Does the included device info say the device is dehydrated? + if device_keys.dehydrated.unwrap_or(false) { + return Ok(true); + } + // If not, fall through and check our existing list of devices + // below, just in case the sender is sending us incorrect + // information embedded in the to-device message, but we know + // better. + } + + // Do we already know about this device? + Ok(self + .store() + .get_device_from_curve_key(sender_user_id, decrypted.result.sender_key) + .await? + .is_some_and(|d| d.is_dehydrated())) } /// Handle a to-device and one-time key counts from a sync response. @@ -1377,6 +1443,14 @@ impl OlmMachine { Ok((events, room_key_updates)) } + /// Initial processing of the changes specified within a sync response. + /// + /// Returns the to-device events (decrypted where needed and where possible) + /// and the processed set of changes. + /// + /// If any of the to-device events in the supplied changes were sent from + /// dehydrated devices, these are not processed, and are omitted from + /// the returned list, as per MSC3814. pub(crate) async fn preprocess_sync_changes( &self, transaction: &mut StoreTransaction, @@ -1412,7 +1486,10 @@ impl OlmMachine { for raw_event in sync_changes.to_device_events { let raw_event = Box::pin(self.receive_to_device_event(transaction, &mut changes, raw_event)).await; - events.push(raw_event); + + if let Some(raw_event) = raw_event { + events.push(raw_event); + } } let changed_sessions = self diff --git a/crates/matrix-sdk-crypto/src/machine/test_helpers.rs b/crates/matrix-sdk-crypto/src/machine/test_helpers.rs index fec64381f..588569932 100644 --- a/crates/matrix-sdk-crypto/src/machine/test_helpers.rs +++ b/crates/matrix-sdk-crypto/src/machine/test_helpers.rs @@ -34,7 +34,7 @@ use ruma::{ use serde_json::json; use crate::{ - store::Changes, + store::{Changes, MemoryStore}, types::{events::ToDeviceEvent, requests::AnyOutgoingRequest}, CrossSigningBootstrapRequests, DeviceData, OlmMachine, }; @@ -102,6 +102,23 @@ pub async fn get_machine_after_query_test_helper() -> (OlmMachine, OneTimeKeys) (machine, otk) } +pub async fn get_machine_pair_using_store( + alice: &UserId, + bob: &UserId, + use_fallback_key: bool, + alice_store: MemoryStore, + alice_device_id: &DeviceId, +) -> (OlmMachine, OlmMachine, OneTimeKeys) { + let (bob, otk) = get_prepared_machine_test_helper(bob, use_fallback_key).await; + + let alice = OlmMachine::with_store(alice, alice_device_id, alice_store, None) + .await + .expect("Failed to create OlmMachine from supplied store"); + + store_each_others_device_data(&alice, &bob).await; + (alice, bob, otk) +} + pub async fn get_machine_pair( alice: &UserId, bob: &UserId, @@ -112,12 +129,34 @@ pub async fn get_machine_pair( let alice_device = alice_device_id(); let alice = OlmMachine::new(alice, alice_device).await; - let alice_device = DeviceData::from_machine_test_helper(&alice).await.unwrap(); - let bob_device = DeviceData::from_machine_test_helper(&bob).await.unwrap(); + store_each_others_device_data(&alice, &bob).await; + (alice, bob, otk) +} + +/// Store alice's device data in bob's store and vice versa +async fn store_each_others_device_data(alice: &OlmMachine, bob: &OlmMachine) { + let alice_device = DeviceData::from_machine_test_helper(alice).await.unwrap(); + let bob_device = DeviceData::from_machine_test_helper(bob).await.unwrap(); alice.store().save_device_data(&[bob_device]).await.unwrap(); bob.store().save_device_data(&[alice_device]).await.unwrap(); +} - (alice, bob, otk) +/// Return a pair of [`OlmMachine`]s, with an olm session created on Alice's +/// side, but with no message yet sent. +/// +/// Create Alice's `OlmMachine` using the [`MemoryStore`] provided +pub async fn get_machine_pair_with_session_using_store( + alice: &UserId, + bob: &UserId, + use_fallback_key: bool, + alice_store: MemoryStore, + alice_device_id: &DeviceId, +) -> (OlmMachine, OlmMachine) { + let (alice, bob, one_time_keys) = + get_machine_pair_using_store(alice, bob, use_fallback_key, alice_store, alice_device_id) + .await; + + build_session_for_pair(alice, bob, one_time_keys).await } /// Return a pair of [`OlmMachine`]s, with an olm session created on Alice's @@ -127,8 +166,20 @@ pub async fn get_machine_pair_with_session( bob: &UserId, use_fallback_key: bool, ) -> (OlmMachine, OlmMachine) { - let (alice, bob, mut one_time_keys) = get_machine_pair(alice, bob, use_fallback_key).await; + let (alice, bob, one_time_keys) = get_machine_pair(alice, bob, use_fallback_key).await; + build_session_for_pair(alice, bob, one_time_keys).await +} + +/// Create a session for the two supplied Olm machines to communicate. +async fn build_session_for_pair( + alice: OlmMachine, + bob: OlmMachine, + mut one_time_keys: BTreeMap< + ruma::OwnedKeyId, + Raw, + >, +) -> (OlmMachine, OlmMachine) { let (device_key_id, one_time_key) = one_time_keys.pop_first().unwrap(); let one_time_keys = BTreeMap::from([( diff --git a/crates/matrix-sdk-crypto/src/machine/tests/mod.rs b/crates/matrix-sdk-crypto/src/machine/tests/mod.rs index b73994dcc..8ab0b9eaa 100644 --- a/crates/matrix-sdk-crypto/src/machine/tests/mod.rs +++ b/crates/matrix-sdk-crypto/src/machine/tests/mod.rs @@ -52,13 +52,17 @@ use crate::{ machine::{ test_helpers::{ get_machine_after_query_test_helper, get_machine_pair_with_session, + get_machine_pair_with_session_using_store, get_machine_pair_with_setup_sessions_test_helper, get_prepared_machine_test_helper, }, EncryptionSyncChanges, OlmMachine, }, olm::{BackedUpRoomKey, ExportedRoomKey, SenderData, VerifyJson}, session_manager::CollectStrategy, - store::{BackupDecryptionKey, Changes, CryptoStore, MemoryStore, RoomKeyInfo}, + store::{ + BackupDecryptionKey, Changes, CryptoStore, DeviceChanges, MemoryStore, PendingChanges, + RoomKeyInfo, + }, types::{ events::{ room::encrypted::{EncryptedToDeviceEvent, ToDeviceEncryptedEventContent}, @@ -70,7 +74,7 @@ use crate::{ }, utilities::json_convert, verification::tests::bob_id, - Account, DecryptionSettings, DeviceData, EncryptionSettings, MegolmError, OlmError, + Account, DecryptionSettings, DeviceData, EncryptionSettings, LocalTrust, MegolmError, OlmError, RoomEventDecryptionResult, TrustRequirement, }; @@ -414,29 +418,98 @@ async fn test_room_key_sharing() { assert_eq!(room_key_updates[0].session_id, alice_session.session_id()); } +#[async_test] +async fn test_to_device_messages_from_dehydrated_devices_are_ignored() { + // Given alice's device is dehydrated + let (alice, bob) = create_dehydrated_machine_and_pair().await; + + // When we send a to-device message from alice to bob + // (Note: we send a room_key message, but it could be any to-device message.) + let room_id = room_id!("!test:example.org"); + let (decrypted, room_key_updates) = + send_room_key_to_device(&alice, &bob, room_id).await.unwrap(); + + // Then the to-device message was discarded, because it was from a dehydrated + // device + assert!(decrypted.is_empty()); + + // And the room key was not imported as a session + let alice_session = + alice.inner.group_session_manager.get_outbound_group_session(room_id).unwrap(); + let session = bob.store().get_inbound_group_session(room_id, alice_session.session_id()).await; + assert!(session.unwrap().is_none()); + + assert!(room_key_updates.is_empty()); +} + +/// "Send" a to-device message containing a room key from sender to receiver. +/// +/// (Actually constructs the JSON of a to-device message from `sender` and feeds +/// it in to `receiver`'s `receive_sync_changes` method. +/// +/// Returns the return value of `receive_sync_changes`, which is a tuple of +/// (decrypted to-device events, updated room keys). async fn send_room_key_to_device( - alice: &OlmMachine, - bob: &OlmMachine, + sender: &OlmMachine, + receiver: &OlmMachine, room_id: &RoomId, ) -> OlmResult<(Vec>, Vec)> { - let to_device_requests = alice - .share_room_key(room_id, iter::once(bob.user_id()), EncryptionSettings::default()) + let to_device_requests = sender + .share_room_key(room_id, iter::once(receiver.user_id()), EncryptionSettings::default()) .await .unwrap(); let event = ToDeviceEvent::new( - alice.user_id().to_owned(), + sender.user_id().to_owned(), to_device_requests_to_content(to_device_requests), ); let event = json_convert(&event).unwrap(); - bob.receive_sync_changes(EncryptionSyncChanges { - to_device_events: vec![event], - changed_devices: &Default::default(), - one_time_keys_counts: &Default::default(), - unused_fallback_keys: None, - next_batch_token: None, - }) + receiver + .receive_sync_changes(EncryptionSyncChanges { + to_device_events: vec![event], + changed_devices: &Default::default(), + one_time_keys_counts: &Default::default(), + unused_fallback_keys: None, + next_batch_token: None, + }) + .await +} + +/// Create an alice, bob pair where alice's device is dehydrated. Create a +/// session for messages from alice to bob, and ensure bob knows alice's device +/// is dehydrated. +async fn create_dehydrated_machine_and_pair() -> (OlmMachine, OlmMachine) { + // Create a store holding info about an account that is linked to a dehydrated + // device. This should never happen in real life, so we have to poke the + // info into the store directly. + let alice_store = MemoryStore::new(); + let alice_dehydrated_account = Account::new_dehydrated(alice_id()); + let mut alice_static_account = alice_dehydrated_account.static_data().clone(); + alice_static_account.dehydrated = true; + let alice_device = DeviceData::from_account(&alice_dehydrated_account); + let alice_dehydrated_device_id = alice_device.device_id().to_owned(); + alice_device.set_trust_state(LocalTrust::Verified); + + let changes = Changes { + devices: DeviceChanges { new: vec![alice_device], ..Default::default() }, + ..Default::default() + }; + alice_store.save_changes(changes).await.expect("Failed to same changes to the store"); + alice_store + .save_pending_changes(PendingChanges { account: Some(alice_dehydrated_account) }) + .await + .expect("Failed to save pending changes to the store"); + + // Create the alice machine using the store we have made (and also create a + // normal bob machine) + get_machine_pair_with_session_using_store( + alice_id(), + user_id(), + false, + alice_store, + &alice_dehydrated_device_id, + ) .await } @@ -854,7 +927,7 @@ async fn test_query_ratcheted_key() { .await .unwrap() .expect("should exist") - .set_trust_state(crate::LocalTrust::Verified); + .set_trust_state(LocalTrust::Verified); alice.create_outbound_group_session_with_defaults_test_helper(room_id).await.unwrap();