Fix not showing display names in already synced rooms (#171)

Fixes #149
This commit is contained in:
Benjamin Grosse 2023-12-18 20:55:04 -08:00 committed by GitHub
parent b33759cbc3
commit 999399a70f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 188 additions and 81 deletions

View file

@ -2,6 +2,7 @@
//! //!
//! The types defined here get used throughout iamb. //! The types defined here get used throughout iamb.
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::hash_map::IntoIter;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::convert::TryFrom; use std::convert::TryFrom;
use std::fmt::{self, Display}; use std::fmt::{self, Display};
@ -1042,6 +1043,38 @@ pub struct SyncInfo {
pub dms: Vec<Arc<(MatrixRoom, Option<Tags>)>>, pub dms: Vec<Arc<(MatrixRoom, Option<Tags>)>>,
} }
bitflags::bitflags! {
/// Load-needs
#[derive(Debug, Default, PartialEq)]
pub struct Need: u32 {
const EMPTY = 0b00000000;
const MESSAGES = 0b00000001;
const MEMBERS = 0b00000010;
}
}
/// Things that need loading for different rooms.
#[derive(Default)]
pub struct RoomNeeds {
needs: HashMap<OwnedRoomId, Need>,
}
impl RoomNeeds {
/// Mark a room for needing something to be loaded.
pub fn insert(&mut self, room_id: OwnedRoomId, need: Need) {
self.needs.entry(room_id).or_default().insert(need);
}
}
impl IntoIterator for RoomNeeds {
type Item = (OwnedRoomId, Need);
type IntoIter = IntoIter<OwnedRoomId, Need>;
fn into_iter(self) -> Self::IntoIter {
self.needs.into_iter()
}
}
/// The main application state. /// The main application state.
pub struct ChatStore { pub struct ChatStore {
/// `:`-commands /// `:`-commands
@ -1066,7 +1099,7 @@ pub struct ChatStore {
pub settings: ApplicationSettings, pub settings: ApplicationSettings,
/// Set of rooms that need more messages loaded in their scrollback. /// Set of rooms that need more messages loaded in their scrollback.
pub need_load: HashSet<OwnedRoomId>, pub need_load: RoomNeeds,
/// [CompletionMap] of Emoji shortcodes. /// [CompletionMap] of Emoji shortcodes.
pub emojis: CompletionMap<String, &'static Emoji>, pub emojis: CompletionMap<String, &'static Emoji>,
@ -1113,11 +1146,6 @@ impl ChatStore {
.unwrap_or_else(|| "Untitled Matrix Room".to_string()) .unwrap_or_else(|| "Untitled Matrix Room".to_string())
} }
/// Mark a room for loading more scrollback.
pub fn mark_for_load(&mut self, room_id: OwnedRoomId) {
self.need_load.insert(room_id);
}
/// Get the [RoomInfo] for a given room identifier. /// Get the [RoomInfo] for a given room identifier.
pub fn get_room_info(&mut self, room_id: OwnedRoomId) -> &mut RoomInfo { pub fn get_room_info(&mut self, room_id: OwnedRoomId) -> &mut RoomInfo {
self.rooms.get_or_default(room_id) self.rooms.get_or_default(room_id)
@ -1659,6 +1687,21 @@ pub mod tests {
); );
} }
#[test]
fn test_need_load() {
let room_id = TEST_ROOM1_ID.clone();
let mut need_load = RoomNeeds::default();
need_load.insert(room_id.clone(), Need::MESSAGES);
need_load.insert(room_id.clone(), Need::MEMBERS);
assert_eq!(need_load.into_iter().collect::<Vec<(OwnedRoomId, Need)>>(), vec![(
room_id,
Need::MESSAGES | Need::MEMBERS,
)],);
}
#[tokio::test] #[tokio::test]
async fn test_complete_msgbar() { async fn test_complete_msgbar() {
let store = mock_store().await; let store = mock_store().await;

View file

@ -77,6 +77,7 @@ use crate::base::{
IambInfo, IambInfo,
IambResult, IambResult,
MessageAction, MessageAction,
Need,
ProgramAction, ProgramAction,
ProgramContext, ProgramContext,
ProgramStore, ProgramStore,
@ -659,6 +660,7 @@ impl Window<IambInfo> for IambWindow {
let (room, name, tags) = store.application.worker.get_room(room_id)?; let (room, name, tags) = store.application.worker.get_room(room_id)?;
let room = RoomState::new(room, name, tags, store); let room = RoomState::new(room, name, tags, store);
store.application.need_load.insert(room.id().to_owned(), Need::MEMBERS);
return Ok(room.into()); return Ok(room.into());
}, },
IambId::DirectList => { IambId::DirectList => {
@ -710,6 +712,7 @@ impl Window<IambInfo> for IambWindow {
let (room, name, tags) = store.application.worker.get_room(room_id)?; let (room, name, tags) = store.application.worker.get_room(room_id)?;
let room = RoomState::new(room, name, tags, store); let room = RoomState::new(room, name, tags, store);
store.application.need_load.insert(room.id().to_owned(), Need::MEMBERS);
Ok(room.into()) Ok(room.into())
} }
} }

View file

@ -1,5 +1,4 @@
//! Message scrollback //! Message scrollback
use std::collections::HashSet;
use ratatui_image::FixedImage; use ratatui_image::FixedImage;
use regex::Regex; use regex::Regex;
@ -73,7 +72,16 @@ use modalkit::editing::{
}; };
use crate::{ use crate::{
base::{IambBufferId, IambInfo, IambResult, ProgramContext, ProgramStore, RoomFocus, RoomInfo}, base::{
IambBufferId,
IambInfo,
IambResult,
Need,
ProgramContext,
ProgramStore,
RoomFocus,
RoomInfo,
},
config::ApplicationSettings, config::ApplicationSettings,
message::{Message, MessageCursor, MessageKey, Messages}, message::{Message, MessageCursor, MessageKey, Messages},
}; };
@ -468,8 +476,7 @@ impl ScrollbackState {
needle: &Regex, needle: &Regex,
mut count: usize, mut count: usize,
info: &RoomInfo, info: &RoomInfo,
need_load: &mut HashSet<OwnedRoomId>, ) -> (Option<MessageCursor>, bool) {
) -> Option<MessageCursor> {
let mut mc = None; let mut mc = None;
for (key, msg) in info.messages.range(..&end).rev() { for (key, msg) in info.messages.range(..&end).rev() {
@ -483,11 +490,7 @@ impl ScrollbackState {
} }
} }
if count > 0 { return (mc, count > 0);
need_load.insert(self.room_id.clone());
}
return mc;
} }
fn find_message( fn find_message(
@ -497,11 +500,10 @@ impl ScrollbackState {
needle: &Regex, needle: &Regex,
count: usize, count: usize,
info: &RoomInfo, info: &RoomInfo,
need_load: &mut HashSet<OwnedRoomId>, ) -> (Option<MessageCursor>, bool) {
) -> Option<MessageCursor> {
match dir { match dir {
MoveDir1D::Next => self.find_message_next(key, needle, count, info), MoveDir1D::Next => (self.find_message_next(key, needle, count, info), false),
MoveDir1D::Previous => self.find_message_prev(key, needle, count, info, need_load), MoveDir1D::Previous => self.find_message_prev(key, needle, count, info),
} }
} }
} }
@ -628,14 +630,14 @@ impl EditorActions<ProgramContext, ProgramStore, IambInfo> for ScrollbackState {
}, },
}; };
self.find_message( let (mc, needs_load) = self.find_message(key, dir, &needle, count, info);
key, if needs_load {
dir, store
&needle, .application
count, .need_load
info, .insert(self.room_id.clone(), Need::MESSAGES);
&mut store.application.need_load, }
) mc
}, },
EditTarget::Search(SearchType::Word(_, _), _, _) => { EditTarget::Search(SearchType::Word(_, _), _, _) => {
let msg = "Cannot perform word search in a list"; let msg = "Cannot perform word search in a list";
@ -714,15 +716,15 @@ impl EditorActions<ProgramContext, ProgramStore, IambInfo> for ScrollbackState {
}, },
}; };
self.find_message( let (mc, needs_load) = self.find_message(key, dir, &needle, count, info);
key, if needs_load {
dir, store
&needle, .application
count, .need_load
info, .insert(self.room_id.to_owned(), Need::MESSAGES);
&mut store.application.need_load, }
)
.map(|c| self._range_to(c)) mc.map(|c| self._range_to(c))
}, },
EditTarget::Search(SearchType::Word(_, _), _, _) => { EditTarget::Search(SearchType::Word(_, _), _, _) => {
let msg = "Cannot perform word search in a list"; let msg = "Cannot perform word search in a list";
@ -1288,7 +1290,10 @@ impl<'a> StatefulWidget for Scrollback<'a> {
let cursor_key = if let Some(k) = cursor.to_key(info) { let cursor_key = if let Some(k) = cursor.to_key(info) {
k k
} else { } else {
self.store.application.mark_for_load(state.room_id.clone()); self.store
.application
.need_load
.insert(state.room_id.to_owned(), Need::MESSAGES);
return; return;
}; };
@ -1389,7 +1394,10 @@ impl<'a> StatefulWidget for Scrollback<'a> {
let first_key = info.messages.first_key_value().map(|(k, _)| k.clone()); let first_key = info.messages.first_key_value().map(|(k, _)| k.clone());
if first_key == state.viewctx.corner.timestamp { if first_key == state.viewctx.corner.timestamp {
// If the top of the screen is the older message, load more. // If the top of the screen is the older message, load more.
self.store.application.mark_for_load(state.room_id.clone()); self.store
.application
.need_load
.insert(state.room_id.to_owned(), Need::MESSAGES);
} }
} }
} }
@ -1427,12 +1435,23 @@ mod tests {
// Search backwards to MSG2. // Search backwards to MSG2.
scrollback.search(prev.clone(), 1.into(), &ctx, &mut store).unwrap(); scrollback.search(prev.clone(), 1.into(), &ctx, &mut store).unwrap();
assert_eq!(scrollback.cursor, MSG2_KEY.clone().into()); assert_eq!(scrollback.cursor, MSG2_KEY.clone().into());
assert_eq!(store.application.need_load.contains(&room_id), false); assert_eq!(
std::mem::take(&mut store.application.need_load)
.into_iter()
.collect::<Vec<(OwnedRoomId, Need)>>()
.is_empty(),
true,
);
// Can't go any further; need_load now contains the room ID. // Can't go any further; need_load now contains the room ID.
scrollback.search(prev.clone(), 1.into(), &ctx, &mut store).unwrap(); scrollback.search(prev.clone(), 1.into(), &ctx, &mut store).unwrap();
assert_eq!(scrollback.cursor, MSG2_KEY.clone().into()); assert_eq!(scrollback.cursor, MSG2_KEY.clone().into());
assert_eq!(store.application.need_load.contains(&room_id), true); assert_eq!(
std::mem::take(&mut store.application.need_load)
.into_iter()
.collect::<Vec<(OwnedRoomId, Need)>>(),
vec![(room_id.clone(), Need::MESSAGES)]
);
// Search forward twice to MSG1. // Search forward twice to MSG1.
scrollback.search(next.clone(), 2.into(), &ctx, &mut store).unwrap(); scrollback.search(next.clone(), 2.into(), &ctx, &mut store).unwrap();

View file

@ -78,6 +78,7 @@ use matrix_sdk::{
use modalkit::editing::action::{EditInfo, InfoMessage, UIError}; use modalkit::editing::action::{EditInfo, InfoMessage, UIError};
use crate::base::Need;
use crate::{ use crate::{
base::{ base::{
AsyncProgramStore, AsyncProgramStore,
@ -187,21 +188,24 @@ async fn update_event_receipts(info: &mut RoomInfo, room: &MatrixRoom, event_id:
} }
} }
async fn load_plan(store: &AsyncProgramStore) -> HashMap<OwnedRoomId, Option<String>> { #[derive(Debug)]
enum Plan {
Messages(OwnedRoomId, Option<String>),
Members(OwnedRoomId),
}
async fn load_plans(store: &AsyncProgramStore) -> Vec<Plan> {
let mut locked = store.lock().await; let mut locked = store.lock().await;
let ChatStore { need_load, rooms, .. } = &mut locked.application; let ChatStore { need_load, rooms, .. } = &mut locked.application;
let mut plan = HashMap::new(); let mut plan = vec![];
for room_id in std::mem::take(need_load).into_iter() { for (room_id, mut need) in std::mem::take(need_load).into_iter() {
if need.contains(Need::MESSAGES) {
let info = rooms.get_or_default(room_id.clone()); let info = rooms.get_or_default(room_id.clone());
if info.recently_fetched() || info.fetching { if !info.recently_fetched() && !info.fetching {
need_load.insert(room_id);
continue;
} else {
info.fetch_last = Instant::now().into(); info.fetch_last = Instant::now().into();
info.fetching = true; info.fetching = true;
}
let fetch_id = match &info.fetch_id { let fetch_id = match &info.fetch_id {
RoomFetchStatus::Done => continue, RoomFetchStatus::Done => continue,
@ -209,12 +213,39 @@ async fn load_plan(store: &AsyncProgramStore) -> HashMap<OwnedRoomId, Option<Str
RoomFetchStatus::NotStarted => None, RoomFetchStatus::NotStarted => None,
}; };
plan.insert(room_id, fetch_id); plan.push(Plan::Messages(room_id.to_owned(), fetch_id));
need.remove(Need::MESSAGES);
}
}
if need.contains(Need::MEMBERS) {
plan.push(Plan::Members(room_id.to_owned()));
need.remove(Need::MEMBERS);
}
if !need.is_empty() {
need_load.insert(room_id, need);
}
} }
return plan; return plan;
} }
async fn run_plan(client: &Client, store: &AsyncProgramStore, plan: Plan) {
match plan {
Plan::Messages(room_id, fetch_id) => {
let limit = MIN_MSG_LOAD;
let client = client.clone();
let store = store.clone();
let res = load_older_one(&client, &room_id, fetch_id, limit).await;
load_insert(room_id, res, store).await;
},
Plan::Members(room_id) => {
let res = members_load(client, &room_id).await;
members_insert(room_id, res, store).await
},
}
}
async fn load_older_one( async fn load_older_one(
client: &Client, client: &Client,
room_id: &RoomId, room_id: &RoomId,
@ -246,15 +277,7 @@ async fn load_older_one(
async fn load_insert(room_id: OwnedRoomId, res: MessageFetchResult, store: AsyncProgramStore) { async fn load_insert(room_id: OwnedRoomId, res: MessageFetchResult, store: AsyncProgramStore) {
let mut locked = store.lock().await; let mut locked = store.lock().await;
let ChatStore { let ChatStore { presences, rooms, worker, picker, settings, .. } = &mut locked.application;
need_load,
presences,
rooms,
worker,
picker,
settings,
..
} = &mut locked.application;
let info = rooms.get_or_default(room_id.clone()); let info = rooms.get_or_default(room_id.clone());
info.fetching = false; info.fetching = false;
let client = &worker.client; let client = &worker.client;
@ -296,33 +319,52 @@ async fn load_insert(room_id: OwnedRoomId, res: MessageFetchResult, store: Async
warn!(room_id = room_id.as_str(), err = e.to_string(), "Failed to load older messages"); warn!(room_id = room_id.as_str(), err = e.to_string(), "Failed to load older messages");
// Wait and try again. // Wait and try again.
need_load.insert(room_id); locked.application.need_load.insert(room_id, Need::MESSAGES);
}, },
} }
} }
async fn load_older(client: &Client, store: &AsyncProgramStore) -> usize { async fn load_older(client: &Client, store: &AsyncProgramStore) -> usize {
let limit = MIN_MSG_LOAD; // Plans are run in parallel. Any room *may* have several plans.
load_plans(store)
// Fetch each room separately, so they don't block each other.
load_plan(store)
.await .await
.into_iter() .into_iter()
.map(|(room_id, fetch_id)| { .map(|plan| run_plan(client, store, plan))
let store = store.clone();
async move {
let res = load_older_one(client, room_id.as_ref(), fetch_id, limit).await;
load_insert(room_id, res, store).await;
}
})
.collect::<FuturesUnordered<_>>() .collect::<FuturesUnordered<_>>()
.count() .count()
.await .await
} }
async fn members_load(client: &Client, room_id: &RoomId) -> IambResult<Vec<RoomMember>> {
if let Some(room) = client.get_room(room_id) {
Ok(room.members_no_sync().await.map_err(IambError::from)?)
} else {
Err(IambError::UnknownRoom(room_id.to_owned()).into())
}
}
async fn members_insert(
room_id: OwnedRoomId,
res: IambResult<Vec<RoomMember>>,
store: &AsyncProgramStore,
) {
if let Ok(members) = res {
let mut locked = store.lock().await;
let ChatStore { rooms, .. } = &mut locked.application;
let info = rooms.get_or_default(room_id.clone());
for member in members {
let user_id = member.user_id();
let display_name =
member.display_name().map_or(user_id.to_string(), |str| str.to_string());
info.display_names.insert(user_id.to_owned(), display_name);
}
}
// else ???
}
async fn load_older_forever(client: &Client, store: &AsyncProgramStore) { async fn load_older_forever(client: &Client, store: &AsyncProgramStore) {
// Load older messages every 2 seconds. // Load any pending older messages or members every 2 seconds.
let mut interval = tokio::time::interval(Duration::from_secs(2)); let mut interval = tokio::time::interval(Duration::from_secs(2));
loop { loop {