1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-11-28 05:03:59 +03:00

store OOB status along with members, to avoid unneccesary fetching

for some small rooms, it is possible that calling /members would not
yield any previously unknown members, as they were all recently active.
This would be the case for most DMs.

For these rooms, we'd end up with 0 OOB members after lazy loading them,
so when getting them out of storage we need a way to distuinguist this case
from never having lazy loaded the members of the room at all.

We store a marker object in the same store and return [] or null accordingly.
This way the /members don't get fetched a second time.
This commit is contained in:
Bruno Windels
2018-07-31 16:56:18 +02:00
parent a8c73f7a4d
commit 5e11bf735e
4 changed files with 94 additions and 27 deletions

View File

@@ -762,7 +762,7 @@ MatrixClient.prototype._loadMembers = async function(room) {
// were the members loaded from the server? // were the members loaded from the server?
let fromServer = false; let fromServer = false;
let rawMembersEvents = await this.store.getOutOfBandMembers(roomId); let rawMembersEvents = await this.store.getOutOfBandMembers(roomId);
if (rawMembersEvents.length == 0) { if (rawMembersEvents === null) {
fromServer = true; fromServer = true;
const lastEventId = room.getLastEventId(); const lastEventId = room.getLastEventId();
const response = await this.members(roomId, "join", "leave", lastEventId); const response = await this.members(roomId, "join", "leave", lastEventId);
@@ -802,18 +802,8 @@ MatrixClient.prototype.loadRoomMembersIfNeeded = async function(roomId) {
const rawMembersEvents = room.currentState.getMembers() const rawMembersEvents = room.currentState.getMembers()
.filter((m) => m.isOutOfBand()) .filter((m) => m.isOutOfBand())
.map((m) => m.events.member.event); .map((m) => m.events.member.event);
// TODO: probably need a way to mark a room as lazy loaded console.log(`LL: telling backend to store ${rawMembersEvents.length} members`);
// even though we didn't store any members, as we'll just await this.store.setOutOfBandMembers(roomId, rawMembersEvents);
// lazy loaded the room in every session. This is a likely
// scenario for DM's where all the members would likely
// be known without lazy loading.
if (rawMembersEvents.length) {
console.log(`LL: telling backend to store ${rawMembersEvents.length} members`);
await this.store.setOutOfBandMembers(roomId, rawMembersEvents);
}
else {
console.log(`LL: no members needed to be stored`);
}
} }
}; };

View File

@@ -34,7 +34,10 @@ function createDatabase(db) {
} }
function upgradeSchemaV2(db) { function upgradeSchemaV2(db) {
const oobMembersStore = db.createObjectStore("oob_membership_events", { keyPath: ["room_id", "state_key"]}); const oobMembersStore = db.createObjectStore(
"oob_membership_events", {
keyPath: ["room_id", "state_key"],
});
oobMembersStore.createIndex("room", "room_id"); oobMembersStore.createIndex("room", "room_id");
} }
@@ -195,6 +198,13 @@ LocalIndexedDBStoreBackend.prototype = {
}); });
}, },
/**
* Returns the out-of-band membership events for this room that
* were previously loaded.
* @param {string} roomId
* @returns {event[]} the events, potentially an empty array if OOB loading didn't yield any new members
* @returns {null} in case the members for this room haven't been stored yet
*/
getOutOfBandMembers: function(roomId) { getOutOfBandMembers: function(roomId) {
return new Promise((resolve, reject) =>{ return new Promise((resolve, reject) =>{
const tx = this.db.transaction(["oob_membership_events"], "readonly"); const tx = this.db.transaction(["oob_membership_events"], "readonly");
@@ -204,36 +214,73 @@ LocalIndexedDBStoreBackend.prototype = {
const request = roomIndex.openCursor(range); const request = roomIndex.openCursor(range);
const membershipEvents = []; const membershipEvents = [];
// did we encounter the oob_written marker object
// amongst the results? That means OOB member
// loading already happened for this room
// but there were no members to persist as they
// were all known already
let oobWritten = false;
request.onsuccess = (event) => { request.onsuccess = (event) => {
const cursor = event.target.result; const cursor = event.target.result;
if (!cursor) { if (!cursor) {
// Unknown room
if (!membershipEvents.length && !oobWritten) {
return resolve(null);
}
return resolve(membershipEvents); return resolve(membershipEvents);
} }
const record = cursor.value; const record = cursor.value;
membershipEvents.push(record); if (record.oob_written) {
oobWritten = true;
} else {
membershipEvents.push(record);
}
cursor.continue(); cursor.continue();
}; };
request.onerror = (err) => { request.onerror = (err) => {
reject(err); reject(err);
}; };
}).then((membershipEvents) => { }).then((events) => {
console.log(`LL: got ${membershipEvents.length} membershipEvents from storage for room ${roomId} ...`); console.log(`LL: got ${events && events.length}` +
return membershipEvents; ` membershipEvents from storage for room ${roomId} ...`);
return events;
}); });
}, },
/**
* Stores the out-of-band membership events for this room. Note that
* it still makes sense to store an empty array as the OOB status for the room is
* marked as fetched, and getOutOfBandMembers will return an empty array instead of null
* @param {string} roomId
* @param {event[]} membershipEvents the membership events to store
* @returns {Promise} when all members have been stored
*/
setOutOfBandMembers: function(roomId, membershipEvents) { setOutOfBandMembers: function(roomId, membershipEvents) {
console.log(`LL: backend about to store ${membershipEvents.length} members for ${roomId}`); console.log(`LL: backend about to store ${membershipEvents.length}` +
function ignoreResult() {}; ` members for ${roomId}`);
function ignoreResult() {}
// run everything in a promise so anything that throws will reject // run everything in a promise so anything that throws will reject
return new Promise((resolve) =>{ return new Promise((resolve) =>{
const tx = this.db.transaction(["oob_membership_events"], "readwrite"); const tx = this.db.transaction(["oob_membership_events"], "readwrite");
const store = tx.objectStore("oob_membership_events"); const store = tx.objectStore("oob_membership_events");
const puts = membershipEvents.map((e) => { const eventPuts = membershipEvents.map((e) => {
let putPromise = promiseifyRequest(store.put(e)); const putPromise = promiseifyRequest(store.put(e));
return putPromise.then(ignoreResult); return putPromise.then(ignoreResult);
}); });
resolve(Promise.all(puts).then(ignoreResult)); // aside from all the events, we also write a marker object to the store
// to mark the fact that OOB members have been written for this room.
// It's possible that 0 members need to be written as all where previously know
// but we still need to know whether to return null or [] from getOutOfBandMembers
// where null means out of band members haven't been stored yet for this room
const markerObject = {
room_id: roomId,
oob_written: true,
state_key: 0,
};
const markerPut = promiseifyRequest(store.put(markerObject));
const allPuts = eventPuts.concat(markerPut);
resolve(Promise.all(allPuts).then(ignoreResult));
}).then(() => { }).then(() => {
console.log(`LL: backend done storing for ${roomId}!`); console.log(`LL: backend done storing for ${roomId}!`);
}); });

View File

@@ -87,12 +87,27 @@ RemoteIndexedDBStoreBackend.prototype = {
return this._doCmd('syncToDatabase', [users]); return this._doCmd('syncToDatabase', [users]);
}, },
/**
* Returns the out-of-band membership events for this room that
* were previously loaded.
* @param {string} roomId
* @returns {event[]} the events, potentially an empty array if OOB loading didn't yield any new members
* @returns {null} in case the members for this room haven't been stored yet
*/
getOutOfBandMembers: function(roomId) { getOutOfBandMembers: function(roomId) {
return this._doCmd('getOutOfBandMembers', [roomId]); return this._doCmd('getOutOfBandMembers', [roomId]);
}, },
setOutOfBandMembers: function(roomId, members) { /**
return this._doCmd('setOutOfBandMembers', [roomId, members]); * Stores the out-of-band membership events for this room. Note that
* it still makes sense to store an empty array as the OOB status for the room is
* marked as fetched, and getOutOfBandMembers will return an empty array instead of null
* @param {string} roomId
* @param {event[]} membershipEvents the membership events to store
* @returns {Promise} when all members have been stored
*/
setOutOfBandMembers: function(roomId, membershipEvents) {
return this._doCmd('setOutOfBandMembers', [roomId, membershipEvents]);
}, },
/** /**

View File

@@ -219,12 +219,27 @@ IndexedDBStore.prototype.setSyncData = function(syncData) {
return this.backend.setSyncData(syncData); return this.backend.setSyncData(syncData);
}; };
/**
* Returns the out-of-band membership events for this room that
* were previously loaded.
* @param {string} roomId
* @returns {event[]} the events, potentially an empty array if OOB loading didn't yield any new members
* @returns {null} in case the members for this room haven't been stored yet
*/
IndexedDBStore.prototype.getOutOfBandMembers = function(roomId) { IndexedDBStore.prototype.getOutOfBandMembers = function(roomId) {
return this.backend.getOutOfBandMembers(roomId); return this.backend.getOutOfBandMembers(roomId);
}; };
IndexedDBStore.prototype.setOutOfBandMembers = function(roomId, members) { /**
return this.backend.setOutOfBandMembers(roomId, members); * Stores the out-of-band membership events for this room. Note that
* it still makes sense to store an empty array as the OOB status for the room is
* marked as fetched, and getOutOfBandMembers will return an empty array instead of null
* @param {string} roomId
* @param {event[]} membershipEvents the membership events to store
* @returns {Promise} when all members have been stored
*/
IndexedDBStore.prototype.setOutOfBandMembers = function(roomId, membershipEvents) {
return this.backend.setOutOfBandMembers(roomId, membershipEvents);
}; };
module.exports.IndexedDBStore = IndexedDBStore; module.exports.IndexedDBStore = IndexedDBStore;