@@ -175,6 +175,42 @@ 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 ( ) ) ) . cloned ( )
193+ . collect ( ) ;
194+ for room_id in to_remove {
195+ println ! ( "unsub {:?}" , room_id) ;
196+ if room_subscriptions. remove ( & room_id) . is_some ( ) {
197+ skip_over_current_sync_loop_iteration = true ;
198+ }
199+ }
200+
201+ if subscribe_to_rooms (
202+ room_subscriptions,
203+ & self . inner . client ,
204+ room_ids,
205+ settings,
206+ cancel_in_flight_request && skip_over_current_sync_loop_iteration,
207+ ) {
208+ self . inner . internal_channel_send_if_possible (
209+ SlidingSyncInternalMessage :: SyncLoopSkipOverCurrentIteration ,
210+ ) ;
211+ }
212+ }
213+
178214 /// Replace all subscriptions to rooms by other ones.
179215 ///
180216 /// If the associated `Room`s exist, they will be marked as members are
@@ -186,15 +222,27 @@ impl SlidingSync {
186222 cancel_in_flight_request : bool ,
187223 ) {
188224 let mut room_subscriptions = self . inner . room_subscriptions . write ( ) . unwrap ( ) ;
189- room_subscriptions. clear ( ) ;
225+
226+ // Remove rooms that are no longer subscribed.
227+ let to_remove: Vec < _ > = room_ids
228+ . iter ( )
229+ . filter ( |room_id| room_subscriptions. contains_key ( * room_id. to_owned ( ) ) )
230+ . collect ( ) ;
231+ let mut any_removed = false ;
232+ for room_id in to_remove {
233+ if room_subscriptions. remove ( * room_id) . is_some ( ) {
234+ any_removed = true ;
235+ }
236+ }
190237
191238 if subscribe_to_rooms (
192239 room_subscriptions,
193240 & self . inner . client ,
194241 room_ids,
195242 settings,
196243 cancel_in_flight_request,
197- ) {
244+ ) || any_removed
245+ {
198246 self . inner . internal_channel_send_if_possible (
199247 SlidingSyncInternalMessage :: SyncLoopSkipOverCurrentIteration ,
200248 ) ;
@@ -1205,6 +1253,136 @@ mod tests {
12051253 Ok ( ( ) )
12061254 }
12071255
1256+ #[ async_test]
1257+ async fn test_resubscribe_to_rooms ( ) -> Result < ( ) > {
1258+ let ( server, sliding_sync) = new_sliding_sync ( vec ! [
1259+ SlidingSyncList :: builder( "foo" )
1260+ . sync_mode( SlidingSyncMode :: new_selective( ) . add_range( 0 ..=10 ) ) ,
1261+ ] )
1262+ . await ?;
1263+
1264+ let stream = sliding_sync. sync ( ) ;
1265+ pin_mut ! ( stream) ;
1266+
1267+ let room_id_0 = room_id ! ( "!r0:bar.org" ) ;
1268+ let room_id_1 = room_id ! ( "!r1:bar.org" ) ;
1269+ let room_id_2 = room_id ! ( "!r2:bar.org" ) ;
1270+
1271+ {
1272+ let _mock_guard = Mock :: given ( SlidingSyncMatcher )
1273+ . respond_with ( ResponseTemplate :: new ( 200 ) . set_body_json ( json ! ( {
1274+ "pos" : "1" ,
1275+ "lists" : { } ,
1276+ "rooms" : {
1277+ room_id_0: {
1278+ "name" : "Room #0" ,
1279+ "initial" : true ,
1280+ } ,
1281+ room_id_1: {
1282+ "name" : "Room #1" ,
1283+ "initial" : true ,
1284+ } ,
1285+ room_id_2: {
1286+ "name" : "Room #2" ,
1287+ "initial" : true ,
1288+ } ,
1289+ }
1290+ } ) ) )
1291+ . mount_as_scoped ( & server)
1292+ . await ;
1293+
1294+ let _ = stream. next ( ) . await . unwrap ( ) ?;
1295+ }
1296+
1297+ let room0 = sliding_sync. inner . client . get_room ( room_id_0) . unwrap ( ) ;
1298+
1299+ // Members aren't synced.
1300+ // We need to make them synced, so that we can test that subscribing to a room
1301+ // make members not synced. That's a desired feature.
1302+ assert ! ( room0. are_members_synced( ) . not( ) ) ;
1303+
1304+ {
1305+ struct MemberMatcher ( OwnedRoomId ) ;
1306+
1307+ impl Match for MemberMatcher {
1308+ fn matches ( & self , request : & Request ) -> bool {
1309+ request. url . path ( )
1310+ == format ! ( "/_matrix/client/r0/rooms/{room_id}/members" , room_id = self . 0 )
1311+ && request. method == Method :: GET
1312+ }
1313+ }
1314+
1315+ let _mock_guard = Mock :: given ( MemberMatcher ( room_id_0. to_owned ( ) ) )
1316+ . respond_with ( ResponseTemplate :: new ( 200 ) . set_body_json ( json ! ( {
1317+ "chunk" : [ ] ,
1318+ } ) ) )
1319+ . mount_as_scoped ( & server)
1320+ . await ;
1321+
1322+ assert_matches ! ( room0. request_members( ) . await , Ok ( ( ) ) ) ;
1323+ }
1324+
1325+ // Members are now synced! We can start subscribing and see how it goes.
1326+ assert ! ( room0. are_members_synced( ) ) ;
1327+
1328+ sliding_sync. resubscribe_to_rooms ( & [ room_id_0, room_id_1] , None , true ) ;
1329+
1330+ // OK, we have subscribed to some rooms. Let's check on `room0` if members are
1331+ // now marked as not synced.
1332+ assert ! ( room0. are_members_synced( ) . not( ) ) ;
1333+
1334+ // Both resubscribed rooms are subscribed.
1335+ {
1336+ let room_subscriptions = sliding_sync. inner . room_subscriptions . read ( ) . unwrap ( ) ;
1337+
1338+ assert ! ( room_subscriptions. contains_key( room_id_0) ) ;
1339+ assert ! ( room_subscriptions. contains_key( room_id_1) ) ;
1340+ assert ! ( !room_subscriptions. contains_key( room_id_2) ) ;
1341+ }
1342+
1343+ // Subscribing to the same room doesn't reset the member sync state.
1344+
1345+ {
1346+ struct MemberMatcher ( OwnedRoomId ) ;
1347+
1348+ impl Match for MemberMatcher {
1349+ fn matches ( & self , request : & Request ) -> bool {
1350+ request. url . path ( )
1351+ == format ! ( "/_matrix/client/r0/rooms/{room_id}/members" , room_id = self . 0 )
1352+ && request. method == Method :: GET
1353+ }
1354+ }
1355+
1356+ let _mock_guard = Mock :: given ( MemberMatcher ( room_id_0. to_owned ( ) ) )
1357+ . respond_with ( ResponseTemplate :: new ( 200 ) . set_body_json ( json ! ( {
1358+ "chunk" : [ ] ,
1359+ } ) ) )
1360+ . mount_as_scoped ( & server)
1361+ . await ;
1362+
1363+ assert_matches ! ( room0. request_members( ) . await , Ok ( ( ) ) ) ;
1364+ }
1365+
1366+ // Members are synced, good, good.
1367+ assert ! ( room0. are_members_synced( ) ) ;
1368+
1369+ sliding_sync. resubscribe_to_rooms ( & [ room_id_0] , None , false ) ;
1370+
1371+ // Members are still synced: because we have already subscribed to the
1372+ // room, the members aren't marked as unsynced.
1373+ assert ! ( room0. are_members_synced( ) ) ;
1374+
1375+ // Only the resubscribed room is subscribed.
1376+ {
1377+ let room_subscriptions = sliding_sync. inner . room_subscriptions . read ( ) . unwrap ( ) ;
1378+
1379+ assert ! ( room_subscriptions. contains_key( room_id_0) ) ;
1380+ assert ! ( !room_subscriptions. contains_key( room_id_1) ) ;
1381+ assert ! ( !room_subscriptions. contains_key( room_id_2) ) ;
1382+ }
1383+ Ok ( ( ) )
1384+ }
1385+
12081386 #[ async_test]
12091387 async fn test_room_subscriptions_are_reset_when_session_expires ( ) -> Result < ( ) > {
12101388 let ( _server, sliding_sync) = new_sliding_sync ( vec ! [
0 commit comments