Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski 2018-03-24 17:52:49 +00:00
commit b5ed08eba2
No known key found for this signature in database
GPG key ID: 3F879DA5AD802A5E
93 changed files with 4188 additions and 653 deletions

View file

@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import EventEmitter from 'events';
import Promise from 'bluebird';
const BULK_REQUEST_DEBOUNCE_MS = 200;
@ -28,8 +29,9 @@ const GROUP_PROFILES_CACHE_BUST_MS = 1800000; // 30 mins
/**
* Stores data used by <Flair/>
*/
class FlairStore {
class FlairStore extends EventEmitter {
constructor(matrixClient) {
super();
this._matrixClient = matrixClient;
this._userGroups = {
// $userId: ['+group1:domain', '+group2:domain', ...]
@ -152,19 +154,29 @@ class FlairStore {
return this._groupProfiles[groupId];
}
// No request yet, start one
if (!this._groupProfilesPromise[groupId]) {
this._groupProfilesPromise[groupId] = matrixClient.getGroupProfile(groupId);
// A request is ongoing, wait for it to complete and return the group profile.
if (this._groupProfilesPromise[groupId]) {
try {
await this._groupProfilesPromise[groupId];
} catch (e) {
// Don't log the error; this is done below
return null;
}
return this._groupProfiles[groupId];
}
// No request yet, start one
console.log('FlairStore: Request group profile of ' + groupId);
this._groupProfilesPromise[groupId] = matrixClient.getGroupProfile(groupId);
let profile;
try {
profile = await this._groupProfilesPromise[groupId];
} catch (e) {
console.log('Failed to get group profile for ' + groupId, e);
console.log('FlairStore: Failed to get group profile for ' + groupId, e);
// Don't retry, but allow a retry when the profile is next requested
delete this._groupProfilesPromise[groupId];
return;
return null;
}
this._groupProfiles[groupId] = {
@ -175,12 +187,24 @@ class FlairStore {
};
delete this._groupProfilesPromise[groupId];
/// XXX: This is verging on recreating a third "Flux"-looking Store. We really
/// should replace FlairStore with a Flux store and some async actions.
console.log('FlairStore: Emit updateGroupProfile for ' + groupId);
this.emit('updateGroupProfile');
setTimeout(() => {
delete this._groupProfiles[groupId];
this.refreshGroupProfile(matrixClient, groupId);
}, GROUP_PROFILES_CACHE_BUST_MS);
return this._groupProfiles[groupId];
}
refreshGroupProfile(matrixClient, groupId) {
// Invalidate the cache
delete this._groupProfiles[groupId];
// Fetch new profile data, and cache it
return this.getGroupProfileCached(matrixClient, groupId);
}
}
if (global.singletonFlairStore === undefined) {

View file

@ -27,6 +27,48 @@ function parseRoomsResponse(response) {
return response.chunk.map((apiRoom) => groupRoomFromApiObject(apiRoom));
}
// The number of ongoing group requests
let ongoingRequestCount = 0;
// This has arbitrarily been set to a small number to lower the priority
// of doing group-related requests because we care about other important
// requests like hitting /sync.
const LIMIT = 3; // Maximum number of ongoing group requests
// FIFO queue of functions to call in the backlog
const backlogQueue = [
// () => {...}
];
// Pull from the FIFO queue
function checkBacklog() {
const item = backlogQueue.shift();
if (typeof item === 'function') item();
}
// Limit the maximum number of ongoing promises returned by fn to LIMIT and
// use a FIFO queue to handle the backlog.
function limitConcurrency(fn) {
return new Promise((resolve, reject) => {
const item = () => {
ongoingRequestCount++;
resolve();
};
if (ongoingRequestCount >= LIMIT) {
// Enqueue this request for later execution
backlogQueue.push(item);
} else {
item();
}
})
.then(fn)
.then((result) => {
ongoingRequestCount--;
checkBacklog();
return result;
});
}
/**
* Stores the group summary for a room and provides an API to change it and
* other useful group APIs that may have an effect on the group summary.
@ -56,23 +98,24 @@ export default class GroupStore extends EventEmitter {
this._fetchResourcePromise = {};
this._resourceFetcher = {
[GroupStore.STATE_KEY.Summary]: () => {
return MatrixClientPeg.get()
.getGroupSummary(this.groupId);
return limitConcurrency(
() => MatrixClientPeg.get().getGroupSummary(this.groupId),
);
},
[GroupStore.STATE_KEY.GroupRooms]: () => {
return MatrixClientPeg.get()
.getGroupRooms(this.groupId)
.then(parseRoomsResponse);
return limitConcurrency(
() => MatrixClientPeg.get().getGroupRooms(this.groupId).then(parseRoomsResponse),
);
},
[GroupStore.STATE_KEY.GroupMembers]: () => {
return MatrixClientPeg.get()
.getGroupUsers(this.groupId)
.then(parseMembersResponse);
return limitConcurrency(
() => MatrixClientPeg.get().getGroupUsers(this.groupId).then(parseMembersResponse),
);
},
[GroupStore.STATE_KEY.GroupInvitedMembers]: () => {
return MatrixClientPeg.get()
.getGroupInvitedUsers(this.groupId)
.then(parseMembersResponse);
return limitConcurrency(
() => MatrixClientPeg.get().getGroupInvitedUsers(this.groupId).then(parseMembersResponse),
);
},
};

View file

@ -23,6 +23,16 @@ import Unread from '../Unread';
* the RoomList.
*/
class RoomListStore extends Store {
static _listOrders = {
"m.favourite": "manual",
"im.vector.fake.invite": "recent",
"im.vector.fake.recent": "recent",
"im.vector.fake.direct": "recent",
"m.lowpriority": "recent",
"im.vector.fake.archived": "recent",
};
constructor() {
super(dis);
@ -68,6 +78,42 @@ class RoomListStore extends Store {
this._generateRoomLists();
}
break;
case 'MatrixActions.Room.timeline': {
if (!this._state.ready ||
!payload.isLiveEvent ||
!payload.isLiveUnfilteredRoomTimelineEvent ||
!this._eventTriggersRecentReorder(payload.event)
) break;
this._generateRoomLists();
}
break;
// When an event is decrypted, it could mean we need to reorder the room
// list because we now know the type of the event.
case 'MatrixActions.Event.decrypted': {
// We may not have synced or done an initial generation of the lists
if (!this._matrixClient || !this._state.ready) break;
const roomId = payload.event.getRoomId();
// We may have decrypted an event without a roomId (e.g to_device)
if (!roomId) break;
const room = this._matrixClient.getRoom(roomId);
// We somehow decrypted an event for a room our client is unaware of
if (!room) break;
const liveTimeline = room.getLiveTimeline();
const eventTimeline = room.getTimelineForEvent(payload.event.getId());
// Either this event was not added to the live timeline (e.g. pagination)
// or it doesn't affect the ordering of the room list.
if (liveTimeline !== eventTimeline ||
!this._eventTriggersRecentReorder(payload.event)
) break;
this._generateRoomLists();
}
break;
case 'MatrixActions.accountData': {
if (payload.event_type !== 'm.direct') break;
this._generateRoomLists();
@ -78,6 +124,14 @@ class RoomListStore extends Store {
this._generateRoomLists();
}
break;
// This could be a new room that we've been invited to, joined or created
// we won't get a RoomMember.membership for these cases if we're not already
// a member.
case 'MatrixActions.Room': {
if (!this._state.ready || !this._matrixClient.credentials.userId) break;
this._generateRoomLists();
}
break;
case 'RoomListActions.tagRoom.pending': {
// XXX: we only show one optimistic update at any one time.
// Ideally we should be making a list of in-flight requests
@ -159,18 +213,9 @@ class RoomListStore extends Store {
}
});
const listOrders = {
"m.favourite": "manual",
"im.vector.fake.invite": "recent",
"im.vector.fake.recent": "recent",
"im.vector.fake.direct": "recent",
"m.lowpriority": "recent",
"im.vector.fake.archived": "recent",
};
Object.keys(lists).forEach((listKey) => {
let comparator;
switch (listOrders[listKey]) {
switch (RoomListStore._listOrders[listKey]) {
case "recent":
comparator = this._recentsComparator;
break;
@ -188,13 +233,17 @@ class RoomListStore extends Store {
});
}
_eventTriggersRecentReorder(ev) {
return ev.getTs() && (
Unread.eventTriggersUnreadCount(ev) ||
ev.getSender() === this._matrixClient.credentials.userId
);
}
_tsOfNewestEvent(room) {
for (let i = room.timeline.length - 1; i >= 0; --i) {
const ev = room.timeline[i];
if (ev.getTs() &&
(Unread.eventTriggersUnreadCount(ev) ||
(ev.getSender() === this._matrixClient.credentials.userId))
) {
if (this._eventTriggersRecentReorder(ev)) {
return ev.getTs();
}
}
@ -210,6 +259,8 @@ class RoomListStore extends Store {
}
_recentsComparator(roomA, roomB) {
// XXX: We could use a cache here and update it when we see new
// events that trigger a reorder
return this._tsOfNewestEvent(roomB) - this._tsOfNewestEvent(roomA);
}