2727//! - `SpaceRoomList`: A component for retrieving a space's children rooms and
2828//! their details.
2929
30- use std:: sync:: Arc ;
30+ use std:: { cmp :: Ordering , collections :: HashMap , sync:: Arc } ;
3131
3232use eyeball_im:: { ObservableVector , VectorSubscriberBatchedStream } ;
3333use futures_util:: pin_mut;
3434use imbl:: Vector ;
35+ use itertools:: Itertools ;
3536use matrix_sdk:: {
3637 Client , Error as SDKError , deserialized_responses:: SyncOrStrippedState , executor:: AbortOnDrop ,
3738} ;
3839use matrix_sdk_common:: executor:: spawn;
3940use ruma:: {
4041 OwnedRoomId , RoomId ,
4142 events:: {
42- SyncStateEvent ,
43+ self , SyncStateEvent ,
4344 space:: { child:: SpaceChildEventContent , parent:: SpaceParentEventContent } ,
4445 } ,
4546} ;
@@ -291,20 +292,46 @@ impl SpaceService {
291292
292293 let root_nodes = graph. root_nodes ( ) ;
293294
294- let joined_space_rooms = joined_spaces
295+ // Proceed with filtering to the top level spaces, sorting them by their
296+ // (optional) order field (as defined in MSC3230) and then mapping them
297+ // to `SpaceRoom`s.
298+ let top_level_spaces = joined_spaces
295299 . iter ( )
296- . filter_map ( |room| {
297- let room_id = room. room_id ( ) ;
300+ . filter ( |room| root_nodes. contains ( & room. room_id ( ) ) )
301+ . collect :: < Vec < _ > > ( ) ;
302+
303+ let mut top_level_space_order = HashMap :: new ( ) ;
304+ for space in & top_level_spaces {
305+ if let Ok ( Some ( raw_event) ) =
306+ space. account_data_static :: < events:: space_order:: SpaceOrderEventContent > ( ) . await
307+ && let Ok ( event) = raw_event. deserialize ( )
308+ {
309+ top_level_space_order. insert ( space. room_id ( ) . to_owned ( ) , event. content . order ) ;
310+ }
311+ }
298312
299- if root_nodes. contains ( & room_id) {
300- Some ( SpaceRoom :: new_from_known ( room, graph. children_of ( room_id) . len ( ) as u64 ) )
301- } else {
302- None
313+ let top_level_spaces = top_level_spaces
314+ . iter ( )
315+ . sorted_by ( |a, b| {
316+ // MSC3230: lexicographically by `order` and then by room ID
317+ match (
318+ top_level_space_order. get ( a. room_id ( ) ) ,
319+ top_level_space_order. get ( b. room_id ( ) ) ,
320+ ) {
321+ ( Some ( a_order) , Some ( b_order) ) => {
322+ a_order. cmp ( b_order) . then ( a. room_id ( ) . cmp ( b. room_id ( ) ) )
323+ }
324+ ( Some ( _) , None ) => Ordering :: Less ,
325+ ( None , Some ( _) ) => Ordering :: Greater ,
326+ ( None , None ) => a. room_id ( ) . cmp ( b. room_id ( ) ) ,
303327 }
304328 } )
329+ . map ( |room| {
330+ SpaceRoom :: new_from_known ( room, graph. children_of ( room. room_id ( ) ) . len ( ) as u64 )
331+ } )
305332 . collect ( ) ;
306333
307- ( joined_space_rooms , graph)
334+ ( top_level_spaces , graph)
308335 }
309336}
310337
@@ -315,9 +342,11 @@ mod tests {
315342 use futures_util:: { StreamExt , pin_mut} ;
316343 use matrix_sdk:: { room:: ParentSpace , test_utils:: mocks:: MatrixMockServer } ;
317344 use matrix_sdk_test:: {
318- JoinedRoomBuilder , LeftRoomBuilder , async_test, event_factory:: EventFactory ,
345+ JoinedRoomBuilder , LeftRoomBuilder , RoomAccountDataTestEvent , async_test,
346+ event_factory:: EventFactory ,
319347 } ;
320- use ruma:: { RoomVersionId , owned_room_id, room_id} ;
348+ use ruma:: { RoomVersionId , UserId , owned_room_id, room_id} ;
349+ use serde_json:: json;
321350 use stream_assert:: { assert_next_eq, assert_pending} ;
322351
323352 use super :: * ;
@@ -554,4 +583,64 @@ mod tests {
554583 vec![ SpaceRoom :: new_from_known( & client. get_room( first_space_id) . unwrap( ) , 0 ) ]
555584 ) ;
556585 }
586+
587+ #[ async_test]
588+ async fn test_top_level_space_order ( ) {
589+ let server = MatrixMockServer :: new ( ) . await ;
590+ let client = server. client_builder ( ) . build ( ) . await ;
591+
592+ server. mock_room_state_encryption ( ) . plain ( ) . mount ( ) . await ;
593+
594+ add_space_rooms_with (
595+ vec ! [
596+ ( room_id!( "!2:a.b" ) , Some ( "2" ) ) ,
597+ ( room_id!( "!4:a.b" ) , None ) ,
598+ ( room_id!( "!3:a.b" ) , None ) ,
599+ ( room_id!( "!1:a.b" ) , Some ( "1" ) ) ,
600+ ] ,
601+ & client,
602+ & server,
603+ & EventFactory :: new ( ) ,
604+ client. user_id ( ) . unwrap ( ) ,
605+ )
606+ . await ;
607+
608+ let space_service = SpaceService :: new ( client. clone ( ) ) ;
609+
610+ // Space with an `order` field set should come first in lexicographic
611+ // order and rest sorted by room ID.
612+ assert_eq ! (
613+ space_service. joined_spaces( ) . await ,
614+ vec![
615+ SpaceRoom :: new_from_known( & client. get_room( room_id!( "!1:a.b" ) ) . unwrap( ) , 0 ) ,
616+ SpaceRoom :: new_from_known( & client. get_room( room_id!( "!2:a.b" ) ) . unwrap( ) , 0 ) ,
617+ SpaceRoom :: new_from_known( & client. get_room( room_id!( "!3:a.b" ) ) . unwrap( ) , 0 ) ,
618+ SpaceRoom :: new_from_known( & client. get_room( room_id!( "!4:a.b" ) ) . unwrap( ) , 0 ) ,
619+ ]
620+ ) ;
621+ }
622+
623+ async fn add_space_rooms_with (
624+ rooms : Vec < ( & RoomId , Option < & str > ) > ,
625+ client : & Client ,
626+ server : & MatrixMockServer ,
627+ factory : & EventFactory ,
628+ user_id : & UserId ,
629+ ) {
630+ for ( room_id, order) in rooms {
631+ let mut builder = JoinedRoomBuilder :: new ( room_id)
632+ . add_state_event ( factory. create ( user_id, RoomVersionId :: V1 ) . with_space_type ( ) ) ;
633+
634+ if let Some ( order) = order {
635+ builder = builder. add_account_data ( RoomAccountDataTestEvent :: Custom ( json ! ( {
636+ "type" : "m.space_order" ,
637+ "content" : {
638+ "order" : order
639+ }
640+ } ) ) ) ;
641+ }
642+
643+ server. sync_room ( client, builder) . await ;
644+ }
645+ }
557646}
0 commit comments