Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Merge pull request #2072 from matrix-org/bwindels/lazy_load_members
Browse files Browse the repository at this point in the history
Lazy load room members - Part I
  • Loading branch information
bwindels committed Jul 26, 2018
2 parents a00c2b7 + cfd20c7 commit 0dd8602
Show file tree
Hide file tree
Showing 10 changed files with 81 additions and 83 deletions.
12 changes: 12 additions & 0 deletions src/MatrixClientPeg.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ interface MatrixClientCreds {
guest: boolean,
}

const FILTER_CONTENT = {
room: {
state: {
lazy_load_members: true,
},
},
};

/**
* Wrapper object for handling the js-sdk Matrix Client object in the react-sdk
* Handles the creation/initialisation of client objects.
Expand Down Expand Up @@ -99,6 +107,10 @@ class MatrixClientPeg {
// the react sdk doesn't work without this, so don't allow
opts.pendingEventOrdering = "detached";

if (SettingsStore.isFeatureEnabled('feature_lazyloading')) {
opts.filter = await this.matrixClient.createFilter(FILTER_CONTENT);
}

try {
const promise = this.matrixClient.store.startup();
console.log(`MatrixClientPeg: waiting for MatrixClient store to initialise`);
Expand Down
51 changes: 26 additions & 25 deletions src/Rooms.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,26 +31,26 @@ export function getDisplayAliasForRoom(room) {
* If the room contains only two members including the logged-in user,
* return the other one. Otherwise, return null.
*/
export function getOnlyOtherMember(room, me) {
const joinedMembers = room.getJoinedMembers();
export function getOnlyOtherMember(room, myUserId) {

if (joinedMembers.length === 2) {
return joinedMembers.filter(function(m) {
return m.userId !== me.userId;
if (room.currentState.getJoinedMemberCount() === 2) {
return room.getJoinedMembers().filter(function(m) {
return m.userId !== myUserId;
})[0];
}

return null;
}

function _isConfCallRoom(room, me, conferenceHandler) {
function _isConfCallRoom(room, myUserId, conferenceHandler) {
if (!conferenceHandler) return false;

if (me.membership != "join") {
const myMembership = room.getMyMembership(myUserId);
if (myMembership != "join") {
return false;
}

const otherMember = getOnlyOtherMember(room, me);
const otherMember = getOnlyOtherMember(room, myUserId);
if (otherMember === null) {
return false;
}
Expand All @@ -68,29 +68,30 @@ const isConfCallRoomCache = {
// $roomId: bool
};

export function isConfCallRoom(room, me, conferenceHandler) {
export function isConfCallRoom(room, myUserId, conferenceHandler) {
if (isConfCallRoomCache[room.roomId] !== undefined) {
return isConfCallRoomCache[room.roomId];
}

const result = _isConfCallRoom(room, me, conferenceHandler);
const result = _isConfCallRoom(room, myUserId, conferenceHandler);

isConfCallRoomCache[room.roomId] = result;

return result;
}

export function looksLikeDirectMessageRoom(room, me) {
if (me.membership == "join" || me.membership === "ban" ||
(me.membership === "leave" && me.events.member.getSender() !== me.events.member.getStateKey())) {
export function looksLikeDirectMessageRoom(room, myUserId) {
const myMembership = room.getMyMembership(myUserId);
const me = room.getMember(myUserId);

if (myMembership == "join" || myMembership === "ban" || (me && me.isKicked())) {
// Used to split rooms via tags
const tagNames = Object.keys(room.tags);
// Used for 1:1 direct chats
const members = room.currentState.getMembers();

// Show 1:1 chats in seperate "Direct Messages" section as long as they haven't
// been moved to a different tag section
if (members.length === 2 && !tagNames.length) {
// TODO: Use SUMMARYAPI to take invited users into account
if (room.currentState.getJoinedMemberCount() === 2 && !tagNames.length) {
return true;
}
}
Expand All @@ -100,10 +101,10 @@ export function looksLikeDirectMessageRoom(room, me) {
export function guessAndSetDMRoom(room, isDirect) {
let newTarget;
if (isDirect) {
const guessedTarget = guessDMRoomTarget(
room, room.getMember(MatrixClientPeg.get().credentials.userId),
const guessedUserId = guessDMRoomTargetId(
room, MatrixClientPeg.get().getUserId()
);
newTarget = guessedTarget.userId;
newTarget = guessedUserId;
} else {
newTarget = null;
}
Expand Down Expand Up @@ -159,15 +160,15 @@ export function setDMRoom(roomId, userId) {
* Given a room, estimate which of its members is likely to
* be the target if the room were a DM room and return that user.
*/
export function guessDMRoomTarget(room, me) {
function guessDMRoomTargetId(room, myUserId) {
let oldestTs;
let oldestUser;

// Pick the joined user who's been here longest (and isn't us),
for (const user of room.getJoinedMembers()) {
if (user.userId == me.userId) continue;
if (user.userId == myUserId) continue;

if (oldestTs === undefined || user.events.member.getTs() < oldestTs) {
if (oldestTs === undefined || (user.events.member && user.events.member.getTs() < oldestTs)) {
oldestUser = user;
oldestTs = user.events.member.getTs();
}
Expand All @@ -176,14 +177,14 @@ export function guessDMRoomTarget(room, me) {

// if there are no joined members other than us, use the oldest member
for (const user of room.currentState.getMembers()) {
if (user.userId == me.userId) continue;
if (user.userId == myUserId) continue;

if (oldestTs === undefined || user.events.member.getTs() < oldestTs) {
if (oldestTs === undefined || (user.events.member && user.events.member.getTs() < oldestTs)) {
oldestUser = user;
oldestTs = user.events.member.getTs();
}
}

if (oldestUser === undefined) return me;
if (oldestUser === undefined) return myUserId;
return oldestUser;
}
47 changes: 17 additions & 30 deletions src/components/structures/RoomView.js
Original file line number Diff line number Diff line change
Expand Up @@ -309,9 +309,21 @@ module.exports = React.createClass({
}
});
} else if (room) {
//viewing a previously joined room, try to lazy load members

// Stop peeking because we have joined this room previously
MatrixClientPeg.get().stopPeeking();
this.setState({isPeeking: false});

// lazy load members if enabled
if (SettingsStore.isFeatureEnabled('feature_lazyloading')) {
MatrixClientPeg.get().loadRoomMembersIfNeeded(room.roomId).catch((err) => {
const errorMessage = `Fetching room members for ${this.roomId} failed.` +
" Room members will appear incomplete.";
console.error(errorMessage);
console.error(err);
});
}
}
}
},
Expand Down Expand Up @@ -746,40 +758,15 @@ module.exports = React.createClass({
},

_updateDMState() {
const me = this.state.room.getMember(MatrixClientPeg.get().credentials.userId);
const me = this.state.room.getMember(MatrixClientPeg.get().getUserId());
if (!me || me.membership !== "join") {
return;
}
const roomId = this.state.room.roomId;
const dmInviter = me.getDMInviter();

// The user may have accepted an invite with is_direct set
if (me.events.member.getPrevContent().membership === "invite" &&
me.events.member.getPrevContent().is_direct
) {
// This is a DM with the sender of the invite event (which we assume
// preceded the join event)
Rooms.setDMRoom(
this.state.room.roomId,
me.events.member.getUnsigned().prev_sender,
);
return;
}

const invitedMembers = this.state.room.getMembersWithMembership("invite");
const joinedMembers = this.state.room.getMembersWithMembership("join");

// There must be one invited member and one joined member
if (invitedMembers.length !== 1 || joinedMembers.length !== 1) {
return;
}

// The user may have sent an invite with is_direct sent
const other = invitedMembers[0];
if (other &&
other.membership === "invite" &&
other.events.member.getContent().is_direct
) {
Rooms.setDMRoom(this.state.room.roomId, other.userId);
return;
if (dmInviter) {
Rooms.setDMRoom(roomId, dmInviter);
}
},

Expand Down
2 changes: 1 addition & 1 deletion src/components/views/rooms/MemberInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,7 @@ module.exports = withMatrixClient(React.createClass({

onMemberAvatarClick: function() {
const member = this.props.member;
const avatarUrl = member.user ? member.user.avatarUrl : member.events.member.getContent().avatar_url;
const avatarUrl = member.getMxcAvatarUrl();
if (!avatarUrl) return;

const httpUrl = this.props.matrixClient.mxcUrlToHttp(avatarUrl);
Expand Down
4 changes: 2 additions & 2 deletions src/components/views/rooms/RoomList.js
Original file line number Diff line number Diff line change
Expand Up @@ -342,8 +342,8 @@ module.exports = React.createClass({
if (!taggedRoom) {
return;
}
const me = taggedRoom.getMember(MatrixClientPeg.get().credentials.userId);
if (HIDE_CONFERENCE_CHANS && Rooms.isConfCallRoom(taggedRoom, me, this.props.ConferenceHandler)) {
const myUserId = MatrixClientPeg.get().getUserId();
if (HIDE_CONFERENCE_CHANS && Rooms.isConfCallRoom(taggedRoom, myUserId, this.props.ConferenceHandler)) {
return;
}

Expand Down
14 changes: 5 additions & 9 deletions src/components/views/rooms/RoomPreviewBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,11 @@ module.exports = React.createClass({
</div>);
}

const myMember = this.props.room ? this.props.room.currentState.members[
MatrixClientPeg.get().credentials.userId
] : null;
const kicked = (
myMember &&
myMember.membership == 'leave' &&
myMember.events.member.getSender() != MatrixClientPeg.get().credentials.userId
);
const banned = myMember && myMember.membership == 'ban';
const myMember = this.props.room ?
this.props.room.getMember(MatrixClientPeg.get().getUserId()) :
null;
const kicked = myMember && myMember.isKicked();
const banned = myMember && myMember && myMember.membership == 'ban';

if (this.props.inviterName) {
let emailMatchBlock;
Expand Down
5 changes: 5 additions & 0 deletions src/settings/Settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ export const SETTINGS = {
supportedLevels: LEVELS_FEATURE,
default: false,
},
"feature_lazyloading": {
isFeature: true,
displayName: _td("Increase performance by loading room members on first view"),
supportedLevels: LEVELS_FEATURE,
},
"MessageComposerInput.dontSuggestEmoji": {
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
displayName: _td('Disable Emoji suggestions while typing'),
Expand Down
14 changes: 7 additions & 7 deletions src/stores/RoomListStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,13 +173,13 @@ class RoomListStore extends Store {
if (!this._matrixClient) return;

this._matrixClient.getRooms().forEach((room, index) => {
const me = room.getMember(this._matrixClient.credentials.userId);
if (!me) return;
const myUserId = this._matrixClient.getUserId();
const membership = room.getMyMembership(myUserId);
const me = room.getMember(myUserId);

if (me.membership == "invite") {
if (membership == "invite") {
lists["im.vector.fake.invite"].push(room);
} else if (me.membership == "join" || me.membership === "ban" ||
(me.membership === "leave" && me.events.member.getSender() !== me.events.member.getStateKey())) {
} else if (membership == "join" || membership === "ban" || (me && me.isKicked())) {
// Used to split rooms via tags
let tagNames = Object.keys(room.tags);

Expand All @@ -206,10 +206,10 @@ class RoomListStore extends Store {
} else {
lists["im.vector.fake.recent"].push(room);
}
} else if (me.membership === "leave") {
} else if (membership === "leave") {
lists["im.vector.fake.archived"].push(room);
} else {
console.error("unrecognised membership: " + me.membership + " - this should never happen");
console.error("unrecognised membership: " + membership + " - this should never happen");
}
});

Expand Down
11 changes: 3 additions & 8 deletions src/utils/DMRoomMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,15 +96,10 @@ export default class DMRoomMap {
if (this.roomToUser[roomId] === undefined) {
// no entry? if the room is an invite, look for the is_direct hint.
const room = this.matrixClient.getRoom(roomId);
// TODO Use SUMMARYAPI to fix DM detection?
if (room) {
const me = room.getMember(this.matrixClient.credentials.userId);
if (me.membership == 'invite') {
// The 'direct' hihnt is there, so declare that this is a DM room for
// whoever invited us.
if (me.events.member.getContent().is_direct) {
return me.events.member.getSender();
}
}
const me = room.getMember(this.matrixClient.getUserId());
return me && me.getDMInviter();
}
}
return this.roomToUser[roomId];
Expand Down
4 changes: 3 additions & 1 deletion test/components/views/rooms/RoomList-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import dis from '../../../../src/dispatcher';
import DMRoomMap from '../../../../src/utils/DMRoomMap.js';
import GroupStore from '../../../../src/stores/GroupStore.js';

import { Room, RoomMember } from 'matrix-js-sdk';
import { MatrixClient, Room, RoomMember } from 'matrix-js-sdk';

function generateRoomId() {
return '!' + Math.random().toString().slice(2, 10) + ':domain';
Expand Down Expand Up @@ -48,6 +48,8 @@ describe('RoomList', () => {
sandbox = TestUtils.stubClient(sandbox);
client = MatrixClientPeg.get();
client.credentials = {userId: myUserId};
//revert this to prototype method as the test-utils monkey-patches this to return a hardcoded value
client.getUserId = MatrixClient.prototype.getUserId;

clock = lolex.install();

Expand Down

0 comments on commit 0dd8602

Please sign in to comment.