@@ -148,6 +148,14 @@ impl From<&MembershipState> for RoomState {
148
148
}
149
149
}
150
150
151
+ /// The number of heroes chosen to compute a room's name, if the room didn't
152
+ /// have a name set by the users themselves.
153
+ ///
154
+ /// A server must return at most 5 heroes, according to the paragraph below
155
+ /// https://spec.matrix.org/v1.10/client-server-api/#get_matrixclientv3sync (grep for "heroes"). We
156
+ /// try to behave similarly here.
157
+ const NUM_HEROES : usize = 5 ;
158
+
151
159
impl Room {
152
160
/// The size of the latest_encrypted_events RingBuffer
153
161
// SAFETY: `new_unchecked` is safe because 10 is not zero.
@@ -596,24 +604,42 @@ impl Room {
596
604
let name = name. trim ( ) ;
597
605
return Ok ( DisplayName :: Named ( name. to_owned ( ) ) ) ;
598
606
}
607
+
599
608
if let Some ( alias) = inner. canonical_alias ( ) {
600
609
let alias = alias. alias ( ) . trim ( ) ;
601
610
return Ok ( DisplayName :: Aliased ( alias. to_owned ( ) ) ) ;
602
611
}
612
+
603
613
inner. summary . clone ( )
604
614
} ;
605
615
606
616
let own_user_id = self . own_user_id ( ) . as_str ( ) ;
607
617
608
- let members: Vec < RoomMember > = if summary. heroes . is_empty ( ) {
609
- self . members ( RoomMemberships :: ACTIVE )
610
- . await ?
611
- . into_iter ( )
612
- . filter ( |u| u. user_id ( ) != own_user_id)
613
- . take ( 5 )
614
- . collect ( )
618
+ let ( heroes, guessed_num_members) : ( Vec < RoomMember > , _ ) = if summary. heroes . is_empty ( ) {
619
+ let mut members = self . members ( RoomMemberships :: ACTIVE ) . await ?;
620
+
621
+ // Make the ordering deterministic.
622
+ members. sort_unstable_by ( |lhs, rhs| lhs. name ( ) . cmp ( rhs. name ( ) ) ) ;
623
+
624
+ // We can make a good prediction of the total number of members here. This might
625
+ // be incorrect if the database info is outdated.
626
+ let guessed_num_members = Some ( members. len ( ) ) ;
627
+
628
+ (
629
+ members
630
+ . into_iter ( )
631
+ . take ( NUM_HEROES )
632
+ . filter ( |u| u. user_id ( ) != own_user_id)
633
+ . collect ( ) ,
634
+ guessed_num_members,
635
+ )
615
636
} else {
616
- let members: Vec < _ > = stream:: iter ( summary. heroes . iter ( ) )
637
+ let mut heroes = summary. heroes ;
638
+
639
+ // Make the ordering deterministic.
640
+ heroes. sort_unstable ( ) ;
641
+
642
+ let members: Vec < _ > = stream:: iter ( heroes. iter ( ) )
617
643
. filter_map ( |u| async move {
618
644
let user_id = UserId :: parse ( u. as_str ( ) ) . ok ( ) ?;
619
645
if user_id == own_user_id {
@@ -626,34 +652,39 @@ impl Room {
626
652
627
653
let members: StoreResult < Vec < _ > > = members. into_iter ( ) . collect ( ) ;
628
654
629
- members?
655
+ ( members?, None )
630
656
} ;
631
657
632
- let ( joined , invited ) = match self . state ( ) {
658
+ let ( num_joined , num_invited ) = match self . state ( ) {
633
659
RoomState :: Invited => {
634
660
// when we were invited we don't have a proper summary, we have to do best
635
661
// guessing
636
- ( members . len ( ) as u64 , 1u64 )
662
+ ( heroes . len ( ) as u64 , 1u64 )
637
663
}
664
+
638
665
RoomState :: Joined if summary. joined_member_count == 0 => {
666
+ let num_members = if let Some ( guessed_num_members) = guessed_num_members {
667
+ guessed_num_members
668
+ } else {
669
+ self . members ( RoomMemberships :: ACTIVE ) . await ?. len ( )
670
+ } ;
671
+
639
672
// joined but the summary is not completed yet
640
- (
641
- ( members. len ( ) as u64 ) + 1 , // we've taken ourselves out of the count
642
- summary. invited_member_count ,
643
- )
673
+ ( num_members as u64 , summary. invited_member_count )
644
674
}
675
+
645
676
_ => ( summary. joined_member_count , summary. invited_member_count ) ,
646
677
} ;
647
678
648
679
debug ! (
649
680
room_id = ?self . room_id( ) ,
650
681
own_user = ?self . own_user_id,
651
- joined , invited ,
652
- heroes = ?members ,
682
+ num_joined , num_invited ,
683
+ heroes = ?heroes ,
653
684
"Calculating name for a room" ,
654
685
) ;
655
686
656
- Ok ( self . inner . read ( ) . base_info . calculate_room_name ( joined , invited , members ) )
687
+ Ok ( self . inner . read ( ) . base_info . calculate_room_name ( num_joined , num_invited , heroes ) )
657
688
}
658
689
659
690
/// Subscribe to the inner `RoomInfo`.
@@ -1919,6 +1950,110 @@ mod tests {
1919
1950
) ;
1920
1951
}
1921
1952
1953
+ #[ async_test]
1954
+ async fn test_display_name_deterministic ( ) {
1955
+ let ( store, room) = make_room ( RoomState :: Joined ) ;
1956
+
1957
+ let alice = user_id ! ( "@alice:example.org" ) ;
1958
+ let bob = user_id ! ( "@bob:example.org" ) ;
1959
+ let carol = user_id ! ( "@carol:example.org" ) ;
1960
+ let denis = user_id ! ( "@denis:example.org" ) ;
1961
+ let erica = user_id ! ( "@erica:example.org" ) ;
1962
+ let fred = user_id ! ( "@fred:example.org" ) ;
1963
+ let me = user_id ! ( "@me:example.org" ) ;
1964
+
1965
+ let mut changes = StateChanges :: new ( "" . to_owned ( ) ) ;
1966
+
1967
+ // Save members in two batches, so that there's no implied ordering in the
1968
+ // store.
1969
+ {
1970
+ let members = changes
1971
+ . state
1972
+ . entry ( room. room_id ( ) . to_owned ( ) )
1973
+ . or_default ( )
1974
+ . entry ( StateEventType :: RoomMember )
1975
+ . or_default ( ) ;
1976
+ members. insert ( carol. into ( ) , make_member_event ( carol, "Carol" ) . cast ( ) ) ;
1977
+ members. insert ( bob. into ( ) , make_member_event ( bob, "Bob" ) . cast ( ) ) ;
1978
+ members. insert ( fred. into ( ) , make_member_event ( fred, "Fred" ) . cast ( ) ) ;
1979
+ members. insert ( me. into ( ) , make_member_event ( me, "Me" ) . cast ( ) ) ;
1980
+ store. save_changes ( & changes) . await . unwrap ( ) ;
1981
+ }
1982
+
1983
+ {
1984
+ let members = changes
1985
+ . state
1986
+ . entry ( room. room_id ( ) . to_owned ( ) )
1987
+ . or_default ( )
1988
+ . entry ( StateEventType :: RoomMember )
1989
+ . or_default ( ) ;
1990
+ members. insert ( alice. into ( ) , make_member_event ( alice, "Alice" ) . cast ( ) ) ;
1991
+ members. insert ( erica. into ( ) , make_member_event ( erica, "Erica" ) . cast ( ) ) ;
1992
+ members. insert ( denis. into ( ) , make_member_event ( denis, "Denis" ) . cast ( ) ) ;
1993
+ store. save_changes ( & changes) . await . unwrap ( ) ;
1994
+ }
1995
+
1996
+ let summary = assign ! ( RumaSummary :: new( ) , {
1997
+ joined_member_count: Some ( 7u32 . into( ) ) ,
1998
+ heroes: vec![ denis. to_string( ) , carol. to_string( ) , bob. to_string( ) , erica. to_string( ) ] ,
1999
+ } ) ;
2000
+ room. inner . update_if ( |info| info. update_summary ( & summary) ) ;
2001
+
2002
+ assert_eq ! (
2003
+ room. computed_display_name( ) . await . unwrap( ) ,
2004
+ DisplayName :: Calculated ( "Bob, Carol, Denis, Erica, and 3 others" . to_owned( ) )
2005
+ ) ;
2006
+ }
2007
+
2008
+ #[ async_test]
2009
+ async fn test_display_name_deterministic_no_heroes ( ) {
2010
+ let ( store, room) = make_room ( RoomState :: Joined ) ;
2011
+
2012
+ let alice = user_id ! ( "@alice:example.org" ) ;
2013
+ let bob = user_id ! ( "@bob:example.org" ) ;
2014
+ let carol = user_id ! ( "@carol:example.org" ) ;
2015
+ let denis = user_id ! ( "@denis:example.org" ) ;
2016
+ let erica = user_id ! ( "@erica:example.org" ) ;
2017
+ let fred = user_id ! ( "@fred:example.org" ) ;
2018
+ let me = user_id ! ( "@me:example.org" ) ;
2019
+
2020
+ let mut changes = StateChanges :: new ( "" . to_owned ( ) ) ;
2021
+
2022
+ // Save members in two batches, so that there's no implied ordering in the
2023
+ // store.
2024
+ {
2025
+ let members = changes
2026
+ . state
2027
+ . entry ( room. room_id ( ) . to_owned ( ) )
2028
+ . or_default ( )
2029
+ . entry ( StateEventType :: RoomMember )
2030
+ . or_default ( ) ;
2031
+ members. insert ( carol. into ( ) , make_member_event ( carol, "Carol" ) . cast ( ) ) ;
2032
+ members. insert ( bob. into ( ) , make_member_event ( bob, "Bob" ) . cast ( ) ) ;
2033
+ members. insert ( fred. into ( ) , make_member_event ( fred, "Fred" ) . cast ( ) ) ;
2034
+ members. insert ( me. into ( ) , make_member_event ( me, "Me" ) . cast ( ) ) ;
2035
+ store. save_changes ( & changes) . await . unwrap ( ) ;
2036
+ }
2037
+
2038
+ {
2039
+ let members = changes
2040
+ . state
2041
+ . entry ( room. room_id ( ) . to_owned ( ) )
2042
+ . or_default ( )
2043
+ . entry ( StateEventType :: RoomMember )
2044
+ . or_default ( ) ;
2045
+ members. insert ( alice. into ( ) , make_member_event ( alice, "Alice" ) . cast ( ) ) ;
2046
+ members. insert ( erica. into ( ) , make_member_event ( erica, "Erica" ) . cast ( ) ) ;
2047
+ members. insert ( denis. into ( ) , make_member_event ( denis, "Denis" ) . cast ( ) ) ;
2048
+ store. save_changes ( & changes) . await . unwrap ( ) ;
2049
+ }
2050
+
2051
+ assert_eq ! (
2052
+ room. computed_display_name( ) . await . unwrap( ) ,
2053
+ DisplayName :: Calculated ( "Alice, Bob, Carol, Denis, Erica, and 2 others" . to_owned( ) )
2054
+ ) ;
2055
+ }
2056
+
1922
2057
#[ async_test]
1923
2058
async fn test_display_name_dm_alone ( ) {
1924
2059
let ( store, room) = make_room ( RoomState :: Joined ) ;
0 commit comments