Skip to content

Commit 7e789cc

Browse files
committed
feat(room_list): make RoomListService updates subscriptions non-destructively
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
1 parent 306b00e commit 7e789cc

4 files changed

Lines changed: 172 additions & 6 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
`RoomListService::subscribe_to_rooms` now updates subscriptions non-destructively.

crates/matrix-sdk-ui/src/room_list_service/mod.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,8 @@ impl RoomListService {
474474
/// room in `room_ids`, so that the [`LatestEventValue`] will automatically
475475
/// be calculated and updated for these rooms, for free.
476476
///
477-
/// All previous room subscriptions will be forgotten.
477+
/// Previous room subscriptions that are not contained in the specified room
478+
/// IDs will be forgotten.
478479
///
479480
/// [listen_to_room]: matrix_sdk::latest_events::LatestEvents::listen_to_room
480481
/// [`LatestEventValue`]: matrix_sdk::latest_events::LatestEventValue
@@ -516,11 +517,7 @@ impl RoomListService {
516517
}
517518

518519
// Subscribe to the rooms.
519-
self.sliding_sync.clear_and_subscribe_to_rooms(
520-
room_ids,
521-
Some(settings),
522-
cancel_in_flight_request,
523-
)
520+
self.sliding_sync.resubscribe_to_rooms(room_ids, Some(settings), cancel_in_flight_request)
524521
}
525522

526523
#[cfg(test)]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add `SlidingSync::resubscribe_to_rooms` for updating room subscriptions non-destructively.

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

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,43 @@ impl SlidingSync {
175175
}
176176
}
177177

178+
/// Add subscriptions to the specified rooms if they don't already exist and
179+
/// remove any existing subscriptions to other rooms.
180+
pub fn resubscribe_to_rooms(
181+
&self,
182+
room_ids: &[&RoomId],
183+
settings: Option<http::request::RoomSubscription>,
184+
cancel_in_flight_request: bool,
185+
) {
186+
let mut room_subscriptions = self.inner.room_subscriptions.write().unwrap();
187+
let mut skip_over_current_sync_loop_iteration = false;
188+
189+
// Remove rooms that are no longer subscribed.
190+
let to_remove: Vec<_> = room_subscriptions
191+
.keys()
192+
.filter(|room_id| !room_ids.contains(&room_id.as_ref()))
193+
.cloned()
194+
.collect();
195+
for room_id in to_remove {
196+
println!("unsub {:?}", room_id);
197+
if room_subscriptions.remove(&room_id).is_some() {
198+
skip_over_current_sync_loop_iteration = true;
199+
}
200+
}
201+
202+
if subscribe_to_rooms(
203+
room_subscriptions,
204+
&self.inner.client,
205+
room_ids,
206+
settings,
207+
cancel_in_flight_request && skip_over_current_sync_loop_iteration,
208+
) {
209+
self.inner.internal_channel_send_if_possible(
210+
SlidingSyncInternalMessage::SyncLoopSkipOverCurrentIteration,
211+
);
212+
}
213+
}
214+
178215
/// Replace all subscriptions to rooms by other ones.
179216
///
180217
/// If the associated `Room`s exist, they will be marked as members are
@@ -1205,6 +1242,136 @@ mod tests {
12051242
Ok(())
12061243
}
12071244

1245+
#[async_test]
1246+
async fn test_resubscribe_to_rooms() -> Result<()> {
1247+
let (server, sliding_sync) = new_sliding_sync(vec![
1248+
SlidingSyncList::builder("foo")
1249+
.sync_mode(SlidingSyncMode::new_selective().add_range(0..=10)),
1250+
])
1251+
.await?;
1252+
1253+
let stream = sliding_sync.sync();
1254+
pin_mut!(stream);
1255+
1256+
let room_id_0 = room_id!("!r0:bar.org");
1257+
let room_id_1 = room_id!("!r1:bar.org");
1258+
let room_id_2 = room_id!("!r2:bar.org");
1259+
1260+
{
1261+
let _mock_guard = Mock::given(SlidingSyncMatcher)
1262+
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
1263+
"pos": "1",
1264+
"lists": {},
1265+
"rooms": {
1266+
room_id_0: {
1267+
"name": "Room #0",
1268+
"initial": true,
1269+
},
1270+
room_id_1: {
1271+
"name": "Room #1",
1272+
"initial": true,
1273+
},
1274+
room_id_2: {
1275+
"name": "Room #2",
1276+
"initial": true,
1277+
},
1278+
}
1279+
})))
1280+
.mount_as_scoped(&server)
1281+
.await;
1282+
1283+
let _ = stream.next().await.unwrap()?;
1284+
}
1285+
1286+
let room0 = sliding_sync.inner.client.get_room(room_id_0).unwrap();
1287+
1288+
// Members aren't synced.
1289+
// We need to make them synced, so that we can test that subscribing to a room
1290+
// make members not synced. That's a desired feature.
1291+
assert!(room0.are_members_synced().not());
1292+
1293+
{
1294+
struct MemberMatcher(OwnedRoomId);
1295+
1296+
impl Match for MemberMatcher {
1297+
fn matches(&self, request: &Request) -> bool {
1298+
request.url.path()
1299+
== format!("/_matrix/client/r0/rooms/{room_id}/members", room_id = self.0)
1300+
&& request.method == Method::GET
1301+
}
1302+
}
1303+
1304+
let _mock_guard = Mock::given(MemberMatcher(room_id_0.to_owned()))
1305+
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
1306+
"chunk": [],
1307+
})))
1308+
.mount_as_scoped(&server)
1309+
.await;
1310+
1311+
assert_matches!(room0.request_members().await, Ok(()));
1312+
}
1313+
1314+
// Members are now synced! We can start subscribing and see how it goes.
1315+
assert!(room0.are_members_synced());
1316+
1317+
sliding_sync.resubscribe_to_rooms(&[room_id_0, room_id_1], None, true);
1318+
1319+
// OK, we have subscribed to some rooms. Let's check on `room0` if members are
1320+
// now marked as not synced.
1321+
assert!(room0.are_members_synced().not());
1322+
1323+
// Both resubscribed rooms are subscribed.
1324+
{
1325+
let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1326+
1327+
assert!(room_subscriptions.contains_key(room_id_0));
1328+
assert!(room_subscriptions.contains_key(room_id_1));
1329+
assert!(!room_subscriptions.contains_key(room_id_2));
1330+
}
1331+
1332+
// Subscribing to the same room doesn't reset the member sync state.
1333+
1334+
{
1335+
struct MemberMatcher(OwnedRoomId);
1336+
1337+
impl Match for MemberMatcher {
1338+
fn matches(&self, request: &Request) -> bool {
1339+
request.url.path()
1340+
== format!("/_matrix/client/r0/rooms/{room_id}/members", room_id = self.0)
1341+
&& request.method == Method::GET
1342+
}
1343+
}
1344+
1345+
let _mock_guard = Mock::given(MemberMatcher(room_id_0.to_owned()))
1346+
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
1347+
"chunk": [],
1348+
})))
1349+
.mount_as_scoped(&server)
1350+
.await;
1351+
1352+
assert_matches!(room0.request_members().await, Ok(()));
1353+
}
1354+
1355+
// Members are synced, good, good.
1356+
assert!(room0.are_members_synced());
1357+
1358+
sliding_sync.resubscribe_to_rooms(&[room_id_0], None, false);
1359+
1360+
// Members are still synced: because we have already subscribed to the
1361+
// room, the members aren't marked as unsynced.
1362+
assert!(room0.are_members_synced());
1363+
1364+
// Only the resubscribed room is subscribed.
1365+
{
1366+
let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1367+
1368+
assert!(room_subscriptions.contains_key(room_id_0));
1369+
assert!(!room_subscriptions.contains_key(room_id_1));
1370+
assert!(!room_subscriptions.contains_key(room_id_2));
1371+
}
1372+
Ok(())
1373+
}
1374+
12081375
#[async_test]
12091376
async fn test_room_subscriptions_are_reset_when_session_expires() -> Result<()> {
12101377
let (_server, sliding_sync) = new_sliding_sync(vec![

0 commit comments

Comments
 (0)