Skip to content

Commit 16aa8c6

Browse files
committed
base: sort the heroes in the computed display name alphabetically
1 parent 2987bd1 commit 16aa8c6

File tree

2 files changed

+162
-28
lines changed

2 files changed

+162
-28
lines changed

crates/matrix-sdk-base/src/rooms/mod.rs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ impl BaseRoomInfo {
131131
calculate_room_name(
132132
joined_member_count,
133133
invited_member_count,
134-
heroes.iter().take(3).map(|mem| mem.name()).collect::<Vec<&str>>(),
134+
heroes.iter().map(|mem| mem.name()).collect::<Vec<&str>>(),
135135
)
136136
}
137137

@@ -364,27 +364,26 @@ impl Default for BaseRoomInfo {
364364
}
365365

366366
/// Calculate room name according to step 3 of the [naming algorithm.]
367+
///
368+
/// [naming algorithm]: https://spec.matrix.org/latest/client-server-api/#calculating-the-display-name-for-a-room
367369
fn calculate_room_name(
368370
joined_member_count: u64,
369371
invited_member_count: u64,
370-
heroes: Vec<&str>,
372+
mut heroes: Vec<&str>,
371373
) -> DisplayName {
372374
let heroes_count = heroes.len() as u64;
373375
let invited_joined = invited_member_count + joined_member_count;
374376
let invited_joined_minus_one = invited_joined.saturating_sub(1);
375377

378+
// Stabilize ordering.
379+
heroes.sort_unstable();
380+
376381
let names = if heroes_count >= invited_joined_minus_one {
377-
let mut names = heroes;
378-
// stabilize ordering
379-
names.sort_unstable();
380-
names.join(", ")
382+
heroes.join(", ")
381383
} else if heroes_count < invited_joined_minus_one && invited_joined > 1 {
382-
let mut names = heroes;
383-
names.sort_unstable();
384-
385384
// TODO: What length does the spec want us to use here and in
386385
// the `else`?
387-
format!("{}, and {} others", names.join(", "), (invited_joined - heroes_count))
386+
format!("{}, and {} others", heroes.join(", "), (invited_joined - heroes_count))
388387
} else {
389388
"".to_owned()
390389
};

crates/matrix-sdk-base/src/rooms/normal.rs

Lines changed: 153 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,14 @@ impl From<&MembershipState> for RoomState {
148148
}
149149
}
150150

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+
151159
impl Room {
152160
/// The size of the latest_encrypted_events RingBuffer
153161
// SAFETY: `new_unchecked` is safe because 10 is not zero.
@@ -596,24 +604,42 @@ impl Room {
596604
let name = name.trim();
597605
return Ok(DisplayName::Named(name.to_owned()));
598606
}
607+
599608
if let Some(alias) = inner.canonical_alias() {
600609
let alias = alias.alias().trim();
601610
return Ok(DisplayName::Aliased(alias.to_owned()));
602611
}
612+
603613
inner.summary.clone()
604614
};
605615

606616
let own_user_id = self.own_user_id().as_str();
607617

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+
)
615636
} 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())
617643
.filter_map(|u| async move {
618644
let user_id = UserId::parse(u.as_str()).ok()?;
619645
if user_id == own_user_id {
@@ -626,34 +652,39 @@ impl Room {
626652

627653
let members: StoreResult<Vec<_>> = members.into_iter().collect();
628654

629-
members?
655+
(members?, None)
630656
};
631657

632-
let (joined, invited) = match self.state() {
658+
let (num_joined, num_invited) = match self.state() {
633659
RoomState::Invited => {
634660
// when we were invited we don't have a proper summary, we have to do best
635661
// guessing
636-
(members.len() as u64, 1u64)
662+
(heroes.len() as u64, 1u64)
637663
}
664+
638665
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+
639672
// 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)
644674
}
675+
645676
_ => (summary.joined_member_count, summary.invited_member_count),
646677
};
647678

648679
debug!(
649680
room_id = ?self.room_id(),
650681
own_user = ?self.own_user_id,
651-
joined, invited,
652-
heroes = ?members,
682+
num_joined, num_invited,
683+
heroes = ?heroes,
653684
"Calculating name for a room",
654685
);
655686

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))
657688
}
658689

659690
/// Subscribe to the inner `RoomInfo`.
@@ -1919,6 +1950,110 @@ mod tests {
19191950
);
19201951
}
19211952

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+
19222057
#[async_test]
19232058
async fn test_display_name_dm_alone() {
19242059
let (store, room) = make_room(RoomState::Joined);

0 commit comments

Comments
 (0)