From 23c3ce92d7041cfab4689d243ed25ff68588e1a3 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Thu, 2 Jul 2015 10:13:51 +0100 Subject: [PATCH] Finish scrollback impl. Add UTs. --- lib/store/webstorage.js | 53 ++++++++++++++++++++++++++++++++---- spec/unit/webstorage.spec.js | 30 +++++++++++++++++++- 2 files changed, 76 insertions(+), 7 deletions(-) diff --git a/lib/store/webstorage.js b/lib/store/webstorage.js index c9562ec8f..4ed81329d 100644 --- a/lib/store/webstorage.js +++ b/lib/store/webstorage.js @@ -109,7 +109,7 @@ * * @module store/webstorage */ - +var DEBUG = true; // set true to enable console logging. var utils = require("../utils"); var Room = require("../models/room"); var User = require("../models/user"); @@ -185,10 +185,12 @@ WebStorageStore.prototype.storeRoom = function(room) { WebStorageStore.prototype.getRoom = function(roomId) { // probe if room exists; break early if not. Every room should have state. if (!this.store.getItem(keyName(roomId, "state"))) { + debuglog("getRoom: No room with id %s found.", roomId); return null; } var timelineKeys = getTimelineIndices(this.store, roomId); if (timelineKeys.indexOf("live") !== -1) { + debuglog("getRoom: Live events found. Syncing timeline for %s", roomId); this._syncTimeline(roomId, timelineKeys); } return loadRoom(this.store, roomId, this.batchSize, this._tokens); @@ -264,26 +266,32 @@ WebStorageStore.prototype.getUser = function(userId) { }; /** - * Retrieve scrollback for this room. - * @param {Room} room The matrix room + * Retrieve scrollback for this room. Automatically adds events to the timeline. + * @param {Room} room The matrix room to add the events to the start of the timeline. * @param {integer} limit The max number of old events to retrieve. * @return {Array} An array of objects which will be at most 'limit' * length and at least 0. The objects are the raw event JSON. The last element - * is the 'newest'. + * is the 'oldest' (for parity with homeserver scrollback APIs). */ WebStorageStore.prototype.scrollback = function(room, limit) { if (room.storageToken === undefined || room.storageToken >= this._tokens.length) { return []; } // find the index of the earliest event in this room's timeline + var storeData = this._tokens[room.storageToken] || {}; var i; - var earliestIndex = this._tokens[room.storageToken].earliestIndex; - var earliestEventId = room.timeline[0].getId(); + var earliestIndex = storeData.earliestIndex; + var earliestEventId = room.timeline[0] ? room.timeline[0].getId() : null; + debuglog( + "scrollback in %s (timeline=%s msgs) i=%s, timeline[0].id=%s - req %s events", + room.roomId, room.timeline.length, earliestIndex, earliestEventId, limit + ); var batch = this.store.getItem( keyName(room.roomId, "timeline", earliestIndex) ); if (!batch) { // bad room or already at start, either way we have nothing to give. + debuglog("No batch with index %s found.", earliestIndex); return []; } // populate from this batch first @@ -296,20 +304,34 @@ WebStorageStore.prototype.scrollback = function(room, limit) { var matrixEvent = new MatrixEvent(batch[i]); if (matrixEvent.getId() === earliestEventId) { foundEventId = true; + debuglog( + "Found timeline[0] event at position %s in batch %s", + i, earliestIndex + ); continue; } if (!foundEventId) { continue; } // add entry + debuglog("Add event at position %s in batch %s", i, earliestIndex); scrollback.push(batch[i]); if (scrollback.length === limit) { break; } } if (scrollback.length === limit) { + debuglog("Batch has enough events to satisfy request."); return scrollback; } + if (!foundEventId) { + // the earliest index batch didn't contain the event. In other words, + // this timeline is at a state we don't know, so bail. + debuglog( + "Failed to find event ID %s in batch %s", earliestEventId, earliestIndex + ); + return []; + } // get the requested earlier events from earlier batches while (scrollback.length < limit) { @@ -317,15 +339,28 @@ WebStorageStore.prototype.scrollback = function(room, limit) { batch = this.store.getItem(keyName(room.roomId, "timeline", earliestIndex)); if (!batch) { // no more events + debuglog("No batch found at index %s", earliestIndex); break; } for (i = batch.length - 1; i >= 0; i--) { + debuglog("Add event at position %s in batch %s", i, earliestIndex); scrollback.push(batch[i]); if (scrollback.length === limit) { break; } } } + debuglog( + "Out of %s requested events, returning %s. New index=%s", + limit, scrollback.length, earliestIndex + ); + room.addEventsToTimeline(utils.map(scrollback, function(e) { + return new MatrixEvent(e); + }), true); + + this._tokens[room.storageToken] = { + earliestIndex: earliestIndex + }; return scrollback; }; @@ -516,6 +551,12 @@ function keyName(roomId, key, index) { ); } +function debuglog() { + if (DEBUG) { + console.log.apply(console, arguments); + } +} + /* function delRoomStruct(store, roomId) { var prefix = "room_" + roomId; diff --git a/spec/unit/webstorage.spec.js b/spec/unit/webstorage.spec.js index 8309c23d5..1216998f0 100644 --- a/spec/unit/webstorage.spec.js +++ b/spec/unit/webstorage.spec.js @@ -375,12 +375,39 @@ describe("WebStorageStore", function() { expect(storedRoom.timeline.length).toEqual(3); var events = store.scrollback(storedRoom, 3); expect(events.length).toEqual(3); - // TODO expect(events).toEqual(timeline1); + expect(events.reverse()).toEqual(timeline1); }); it("should give less than 'limit' events near the end of the stored timeline", function() { + var storedRoom = store.getRoom(roomId); + expect(storedRoom.timeline.length).toEqual(3); + var events = store.scrollback(storedRoom, 7); + expect(events.length).toEqual(5); + expect(events.reverse()).toEqual(timeline0.concat(timeline1)); + }); + it("should progressively give older messages the more times scrollback is called", + function() { + var events; + var storedRoom = store.getRoom(roomId); + expect(storedRoom.timeline.length).toEqual(3); + + events = store.scrollback(storedRoom, 2); + expect(events.reverse()).toEqual([timeline1[1], timeline1[2]]); + expect(storedRoom.timeline.length).toEqual(5); + + events = store.scrollback(storedRoom, 2); + expect(events.reverse()).toEqual([timeline0[1], timeline1[0]]); + expect(storedRoom.timeline.length).toEqual(7); + + events = store.scrollback(storedRoom, 2); + expect(events).toEqual([timeline0[0]]); + expect(storedRoom.timeline.length).toEqual(8); + + events = store.scrollback(storedRoom, 2); + expect(events).toEqual([]); + expect(storedRoom.timeline.length).toEqual(8); }); it("should give 0 events if there is no token on the room", function() { @@ -390,6 +417,7 @@ describe("WebStorageStore", function() { it("should given 0 events for unknown rooms", function() { var r = new Room("!unknown:room"); + r.storageToken = "foo"; expect(store.scrollback(r, 3)).toEqual([]); });