Defer fetching user profiles for events
Previously, events for which the sender, target (state_key) or remover was missing from the room members would have their profile fetched from network when registering the event into models. This could cause very slow past events loading times for rooms, since the event registering function (which contained the profile retrieval directives) is run sequentially event-by-event. Missing profiles are now lazy-loaded when events come into the user's view in the QML timeline.
This commit is contained in:
parent
bc5549195b
commit
63af4be1e2
5
TODO.md
5
TODO.md
|
@ -1,13 +1,12 @@
|
||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
- Defer retrieving profiles for events from members not anymore in the room
|
- add room members loading indicator
|
||||||
|
|
||||||
- fix lag when clicking accounts in the AccountBar with a very long room list
|
- fix lag when clicking accounts in the AccountBar with a very long room list
|
||||||
|
|
||||||
- fix: on startup, if a room's last event is a membership change,
|
- fix: on startup, if a room's last event is a membership change,
|
||||||
it won't be visible in timeline no matter what the user config is
|
it won't be visible in timeline no matter what the user config is
|
||||||
- fix: there are rooms without messages on first sync
|
- fix: there are rooms without messages on first sync
|
||||||
- avatar loading performance problem?
|
- fix binding loops?
|
||||||
|
|
||||||
- update docstrings
|
- update docstrings
|
||||||
- update flatpak nio required version
|
- update flatpak nio required version
|
||||||
|
|
|
@ -221,10 +221,10 @@ class Backend:
|
||||||
async def get_profile(self, user_id: str) -> nio.ProfileGetResponse:
|
async def get_profile(self, user_id: str) -> nio.ProfileGetResponse:
|
||||||
"""Cache and return the matrix profile of `user_id`."""
|
"""Cache and return the matrix profile of `user_id`."""
|
||||||
|
|
||||||
if user_id in self.profile_cache:
|
|
||||||
return self.profile_cache[user_id]
|
|
||||||
|
|
||||||
async with self.get_profile_locks[user_id]:
|
async with self.get_profile_locks[user_id]:
|
||||||
|
if user_id in self.profile_cache:
|
||||||
|
return self.profile_cache[user_id]
|
||||||
|
|
||||||
client = await self.get_any_client()
|
client = await self.get_any_client()
|
||||||
response = await client.get_profile(user_id)
|
response = await client.get_profile(user_id)
|
||||||
|
|
||||||
|
|
|
@ -1283,49 +1283,89 @@ class MatrixClient(nio.AsyncClient):
|
||||||
HTML.rooms_user_id_names[room.room_id].pop(user_id, None)
|
HTML.rooms_user_id_names[room.room_id].pop(user_id, None)
|
||||||
|
|
||||||
|
|
||||||
async def get_member_name_avatar(
|
async def get_event_profiles(self, room_id: str, event_id: str) -> None:
|
||||||
self, room_id: str, user_id: str,
|
"""Fetch from network an event's sender, target and remover's profile.
|
||||||
) -> Tuple[str, str]:
|
|
||||||
"""Return a room member's display name and avatar.
|
|
||||||
|
|
||||||
If the member isn't found in the room (e.g. they left), their
|
This should be called from QML, see `MatrixClient.get_member_profile`'s
|
||||||
|
docstring.
|
||||||
|
"""
|
||||||
|
|
||||||
|
ev: Event = self.models[self.user_id, room_id, "events"][event_id]
|
||||||
|
|
||||||
|
if not ev.fetch_profile:
|
||||||
|
return
|
||||||
|
|
||||||
|
get_profile = partial(
|
||||||
|
self.get_member_profile, room_id, can_fetch_from_network=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not ev.sender_name and not ev.sender_avatar:
|
||||||
|
sender_name, sender_avatar, _ = await get_profile(ev.sender_id)
|
||||||
|
ev.sender_name = sender_name
|
||||||
|
ev.sender_avatar = sender_avatar
|
||||||
|
|
||||||
|
if ev.target_id and not ev.target_name and not ev.target_avatar:
|
||||||
|
target_name, target_avatar, _ = await get_profile(ev.target_id)
|
||||||
|
ev.target_name = target_name
|
||||||
|
ev.target_avatar = target_avatar
|
||||||
|
|
||||||
|
if ev.redacter_id and not ev.redacter_name:
|
||||||
|
redacter_name, _, _ = await get_profile(ev.target_id)
|
||||||
|
ev.redacter_name = redacter_name
|
||||||
|
|
||||||
|
ev.fetch_profile = False
|
||||||
|
|
||||||
|
|
||||||
|
async def get_member_profile(
|
||||||
|
self, room_id: str, user_id: str, can_fetch_from_network: bool = False,
|
||||||
|
) -> Tuple[str, str, bool]:
|
||||||
|
"""Return a room member's (display_name, avatar, should_lazy_fetch)
|
||||||
|
|
||||||
|
The returned tuple's last element tells whether
|
||||||
|
`MatrixClient.get_event_profiles()` should be called by QML
|
||||||
|
with `can_fetch_from_network = True` when appropriate,
|
||||||
|
e.g. when this message comes in the user's view.
|
||||||
|
|
||||||
|
If the member isn't found in the room (e.g. they left) and
|
||||||
|
`can_fetch_from_network` is `True`, their
|
||||||
profile is retrieved using `MatrixClient.backend.get_profile()`.
|
profile is retrieved using `MatrixClient.backend.get_profile()`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
item = self.models[self.user_id, room_id, "members"][user_id]
|
member = self.models[self.user_id, room_id, "members"][user_id]
|
||||||
|
return (member.display_name, member.avatar_url, False)
|
||||||
|
|
||||||
|
except KeyError: # e.g. member is not in the room anymore
|
||||||
|
if not can_fetch_from_network:
|
||||||
|
return ("", "", True)
|
||||||
|
|
||||||
except KeyError: # e.g. user is not anymore in the room
|
|
||||||
try:
|
try:
|
||||||
info = await self.backend.get_profile(user_id)
|
info = await self.backend.get_profile(user_id)
|
||||||
return (info.displayname or "", info.avatar_url or "")
|
return (info.displayname or "", info.avatar_url or "", False)
|
||||||
|
|
||||||
except MatrixError:
|
except MatrixError:
|
||||||
return ("", "")
|
return ("", "", False)
|
||||||
|
|
||||||
else:
|
|
||||||
return (item.display_name, item.avatar_url)
|
|
||||||
|
|
||||||
|
|
||||||
async def register_nio_event(
|
async def register_nio_event(
|
||||||
self,
|
self,
|
||||||
room: nio.MatrixRoom,
|
room: nio.MatrixRoom,
|
||||||
ev: nio.Event,
|
ev: nio.Event,
|
||||||
event_id: str = "",
|
event_id: str = "",
|
||||||
|
override_fetch_profile: Optional[bool] = None,
|
||||||
**fields,
|
**fields,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Register a `nio.Event` as a `Event` object in our model."""
|
"""Register a `nio.Event` as a `Event` object in our model."""
|
||||||
|
|
||||||
await self.register_nio_room(room)
|
await self.register_nio_room(room)
|
||||||
|
|
||||||
sender_name, sender_avatar = \
|
sender_name, sender_avatar, must_fetch_sender = \
|
||||||
await self.get_member_name_avatar(room.room_id, ev.sender)
|
await self.get_member_profile(room.room_id, ev.sender)
|
||||||
|
|
||||||
target_id = getattr(ev, "state_key", "") or ""
|
target_id = getattr(ev, "state_key", "") or ""
|
||||||
|
|
||||||
target_name, target_avatar = \
|
target_name, target_avatar, must_fetch_target = \
|
||||||
await self.get_member_name_avatar(room.room_id, target_id) \
|
await self.get_member_profile(room.room_id, target_id) \
|
||||||
if target_id else ("", "")
|
if target_id else ("", "", False)
|
||||||
|
|
||||||
content = fields.get("content", "").strip()
|
content = fields.get("content", "").strip()
|
||||||
|
|
||||||
|
@ -1349,6 +1389,10 @@ class MatrixClient(nio.AsyncClient):
|
||||||
target_name = target_name,
|
target_name = target_name,
|
||||||
target_avatar = target_avatar,
|
target_avatar = target_avatar,
|
||||||
links = Event.parse_links(content),
|
links = Event.parse_links(content),
|
||||||
|
fetch_profile =
|
||||||
|
(must_fetch_sender or must_fetch_target)
|
||||||
|
if override_fetch_profile is None else
|
||||||
|
override_fetch_profile,
|
||||||
**fields,
|
**fields,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -217,6 +217,7 @@ class Event(ModelItem):
|
||||||
sender_id: str = field()
|
sender_id: str = field()
|
||||||
sender_name: str = field()
|
sender_name: str = field()
|
||||||
sender_avatar: str = field()
|
sender_avatar: str = field()
|
||||||
|
fetch_profile: bool = False
|
||||||
|
|
||||||
content: str = ""
|
content: str = ""
|
||||||
inline_content: str = ""
|
inline_content: str = ""
|
||||||
|
|
|
@ -196,6 +196,10 @@ class NioCallbacks:
|
||||||
|
|
||||||
|
|
||||||
async def onRedactedEvent(self, room, ev, event_id: str = "") -> None:
|
async def onRedactedEvent(self, room, ev, event_id: str = "") -> None:
|
||||||
|
redacter_name, _, must_fetch_redacter = \
|
||||||
|
await self.client.get_member_profile(room.room_id, ev.redacter) \
|
||||||
|
if ev.redacter else ("", "", False)
|
||||||
|
|
||||||
await self.client.register_nio_event(
|
await self.client.register_nio_event(
|
||||||
room,
|
room,
|
||||||
ev,
|
ev,
|
||||||
|
@ -206,11 +210,9 @@ class NioCallbacks:
|
||||||
type(ev), ev.redacter, ev.sender, ev.reason,
|
type(ev), ev.redacter, ev.sender, ev.reason,
|
||||||
),
|
),
|
||||||
|
|
||||||
redacter_id = ev.redacter or "",
|
redacter_id = ev.redacter or "",
|
||||||
redacter_name =
|
redacter_name = redacter_name,
|
||||||
(await self.client.get_member_name_avatar(
|
override_fetch_profile = True,
|
||||||
room.room_id, ev.redacter,
|
|
||||||
))[0] if ev.redacter else "",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,8 @@ HColumnLayout {
|
||||||
|
|
||||||
property var hoveredMediaTypeUrl: []
|
property var hoveredMediaTypeUrl: []
|
||||||
|
|
||||||
|
property var fetchProfilesFuture: null
|
||||||
|
|
||||||
// Remember timeline goes from newest message at index 0 to oldest
|
// Remember timeline goes from newest message at index 0 to oldest
|
||||||
readonly property var previousModel: eventList.model.get(model.index + 1)
|
readonly property var previousModel: eventList.model.get(model.index + 1)
|
||||||
readonly property var nextModel: eventList.model.get(model.index - 1)
|
readonly property var nextModel: eventList.model.get(model.index - 1)
|
||||||
|
@ -64,6 +66,13 @@ HColumnLayout {
|
||||||
// HSelectableLabel's MouseArea hover events
|
// HSelectableLabel's MouseArea hover events
|
||||||
onCursorShapeChanged: eventList.cursorShape = cursorShape
|
onCursorShapeChanged: eventList.cursorShape = cursorShape
|
||||||
|
|
||||||
|
Component.onCompleted: if (model.fetch_profile) py.callClientCoro(
|
||||||
|
chat.userId, "get_event_profiles", [chat.roomId, model.id],
|
||||||
|
)
|
||||||
|
|
||||||
|
Component.onDestruction:
|
||||||
|
if (fetchProfilesFuture) fetchProfilesFuture.cancel()
|
||||||
|
|
||||||
|
|
||||||
function json() {
|
function json() {
|
||||||
let event = ModelStore.get(chat.userId, chat.roomId, "events")
|
let event = ModelStore.get(chat.userId, chat.roomId, "events")
|
||||||
|
|
Loading…
Reference in New Issue
Block a user