diff --git a/lib/client.js b/lib/client.js index 72e22b3a9..d731259d6 100644 --- a/lib/client.js +++ b/lib/client.js @@ -27,6 +27,7 @@ var q = require("q"); var httpApi = require("./http-api"); var MatrixEvent = require("./models/event").MatrixEvent; var EventStatus = require("./models/event").EventStatus; +var EventTimeline = require("./models/event-timeline"); var SearchResult = require("./models/search-result"); var StubStore = require("./store/stub"); var webRtcCall = require("./webrtc/call"); @@ -2155,7 +2156,7 @@ MatrixClient.prototype.getEventTimeline = function(room, eventId) { timeline = room.addTimeline(); timeline.initialiseState(utils.map(res.state, self.getEventMapper())); - timeline.getState(false).paginationToken = res.end; + timeline.getState(EventTimeline.FORWARDS).paginationToken = res.end; } room.addEventsToTimeline(matrixEvents, true, timeline, res.start); @@ -2194,13 +2195,14 @@ MatrixClient.prototype.paginateEventTimeline = function(eventTimeline, opts) { throw new Error("Unknown room " + eventTimeline.getRoomId()); } - var token = eventTimeline.getPaginationToken(backwards); + var dir = backwards ? EventTimeline.BACKWARDS : EventTimeline.FORWARDS; + + var token = eventTimeline.getPaginationToken(dir); if (!token) { // no token - no results. return q(false); } - var dir = backwards ? 'b' : 'f'; var pendingRequest = eventTimeline._paginationRequests[dir]; if (pendingRequest) { @@ -2230,7 +2232,7 @@ MatrixClient.prototype.paginateEventTimeline = function(eventTimeline, opts) { // paginate. We need to keep the 'forwards' token though, to make sure // we can recover from gappy syncs. if (backwards && res.end == res.start) { - eventTimeline.setPaginationToken(null, true); + eventTimeline.setPaginationToken(null, dir); } return res.end != res.start; }).finally(function() { diff --git a/lib/models/event-timeline.js b/lib/models/event-timeline.js index 2d3cb8e93..13f2a721d 100644 --- a/lib/models/event-timeline.js +++ b/lib/models/event-timeline.js @@ -44,6 +44,18 @@ function EventTimeline(roomId) { this._paginationRequests = {'b': null, 'f': null}; } +/** + * Symbolic constant for methods which take a 'direction' argument: + * refers to the start of the timeline, or backwards in time. + */ +EventTimeline.BACKWARDS = "b"; + +/** + * Symbolic constant for methods which take a 'direction' argument: + * refers to the end of the timeline, or forwards in time. + */ +EventTimeline.FORWARDS = "f"; + /** * Initialise the start and end state with the given events * @@ -104,47 +116,65 @@ EventTimeline.prototype.getEvents = function() { /** * Get the room state at the start/end of the timeline * - * @param {boolean} start true to get the state at the start of the timeline; - * false to get the state at the end of the timeline. + * @param {string} direction EventTimeline.BACKWARDS to get the state at the + * start of the timeline; EventTimeline.FORWARDS to get the state at the end + * of the timeline. + * * @return {RoomState} state at the start/end of the timeline */ -EventTimeline.prototype.getState = function(start) { - return start ? this._startState : this._endState; +EventTimeline.prototype.getState = function(direction) { + if (direction == EventTimeline.BACKWARDS) { + return this._startState; + } else if (direction == EventTimeline.FORWARDS) { + return this._endState; + } else { + throw new Error("Invalid direction '" + direction + "'"); + } }; /** * Get a pagination token * - * @param {boolean} backwards true to get the pagination token for going - * backwards in time + * @param {string} direction EventTimeline.BACKWARDS to get the pagination + * token for going backwards in time; EventTimeline.FORWARDS to get the + * pagination token for going forwards in time. + * * @return {?string} pagination token */ -EventTimeline.prototype.getPaginationToken = function(backwards) { - return this.getState(backwards).paginationToken; +EventTimeline.prototype.getPaginationToken = function(direction) { + return this.getState(direction).paginationToken; }; /** * Set a pagination token * * @param {?string} token pagination token - * @param {boolean} backwards true to set the pagination token for going - * backwards in time + * + * @param {string} direction EventTimeline.BACKWARDS to set the pagination + * token for going backwards in time; EventTimeline.FORWARDS to set the + * pagination token for going forwards in time. */ -EventTimeline.prototype.setPaginationToken = function(token, backwards) { - this.getState(backwards).paginationToken = token; +EventTimeline.prototype.setPaginationToken = function(token, direction) { + this.getState(direction).paginationToken = token; }; /** * Get the next timeline in the series * - * @param {boolean} before true to get the previous timeline; false to get the - * following one + * @param {string} direction EventTimeline.BACKWARDS to get the previous + * timeline; EventTimeline.FORWARDS to get the next timeline. * * @return {?EventTimeline} previous or following timeline, if they have been * joined up. */ -EventTimeline.prototype.getNeighbouringTimeline = function(before) { - return before ? this._prevTimeline : this._nextTimeline; +EventTimeline.prototype.getNeighbouringTimeline = function(direction) { + if (direction == EventTimeline.BACKWARDS) { + return this._prevTimeline; + } else if (direction == EventTimeline.FORWARDS) { + return this._nextTimeline; + } else { + throw new Error("Invalid direction '" + direction + "'"); + } }; /** @@ -152,25 +182,28 @@ EventTimeline.prototype.getNeighbouringTimeline = function(before) { * * @param {EventTimeline} neighbour previous/following timeline * - * @param {boolean} before true to set the previous timeline; false to set - * following one. + * @param {string} direction EventTimeline.BACKWARDS to set the previous + * timeline; EventTimeline.FORWARDS to set the next timeline. * * @throws {Error} if an attempt is made to set the neighbouring timeline when * it is already set. */ -EventTimeline.prototype.setNeighbouringTimeline = function(neighbour, before) { - if (this.getNeighbouringTimeline(before)) { +EventTimeline.prototype.setNeighbouringTimeline = function(neighbour, direction) { + if (this.getNeighbouringTimeline(direction)) { throw new Error("timeline already has a neighbouring timeline - " + "cannot reset neighbour"); } - if (before) { + + if (direction == EventTimeline.BACKWARDS) { this._prevTimeline = neighbour; - } else { + } else if (direction == EventTimeline.FORWARDS) { this._nextTimeline = neighbour; + } else { + throw new Error("Invalid direction '" + direction + "'"); } // make sure we don't try to paginate this timeline - this.setPaginationToken(null, before); + this.setPaginationToken(null, direction); }; /** diff --git a/lib/models/room.js b/lib/models/room.js index 7287f3436..c6be68362 100644 --- a/lib/models/room.js +++ b/lib/models/room.js @@ -181,7 +181,7 @@ Room.prototype.resetLiveTimeline = function() { // this method is called by our own constructor. // initialise the state in the new timeline from our last known state - var evMap = this._liveTimeline.getState(false).events; + var evMap = this._liveTimeline.getState(EventTimeline.FORWARDS).events; var events = []; for (var evtype in evMap) { if (!evMap.hasOwnProperty(evtype)) { continue; } @@ -199,8 +199,8 @@ Room.prototype.resetLiveTimeline = function() { // state at the start and end of that timeline. These are more // for backwards-compatibility than anything else. this.timeline = this._liveTimeline.getEvents(); - this.oldState = this._liveTimeline.getState(true); - this.currentState = this._liveTimeline.getState(false); + this.oldState = this._liveTimeline.getState(EventTimeline.BACKWARDS); + this.currentState = this._liveTimeline.getState(EventTimeline.FORWARDS); }; /** @@ -377,6 +377,11 @@ Room.prototype.addEventsToTimeline = function(events, toStartOfTimeline, return; } + var direction = toStartOfTimeline ? EventTimeline.BACKWARDS : + EventTimeline.FORWARDS; + var inverseDirection = toStartOfTimeline ? EventTimeline.FORWARDS : + EventTimeline.BACKWARDS; + // Adding events to timelines can be quite complicated. The following // illustrates some of the corner-cases. // @@ -460,7 +465,7 @@ Room.prototype.addEventsToTimeline = function(events, toStartOfTimeline, continue; } - var neighbour = timeline.getNeighbouringTimeline(toStartOfTimeline); + var neighbour = timeline.getNeighbouringTimeline(direction); if (neighbour) { // this timeline already has a neighbour in the relevant direction; // let's assume the timelines are already correctly linked up, and @@ -486,13 +491,13 @@ Room.prototype.addEventsToTimeline = function(events, toStartOfTimeline, console.info("Already have timeline for " + eventId + " - joining timeline " + timeline + " to " + existingTimeline); - timeline.setNeighbouringTimeline(existingTimeline, toStartOfTimeline); - existingTimeline.setNeighbouringTimeline(timeline, !toStartOfTimeline); + timeline.setNeighbouringTimeline(existingTimeline, direction); + existingTimeline.setNeighbouringTimeline(timeline, inverseDirection); timeline = existingTimeline; } if (updateToken) { - timeline.setPaginationToken(paginationToken, toStartOfTimeline); + timeline.setPaginationToken(paginationToken, direction); } }; @@ -644,7 +649,7 @@ Room.prototype.addEvents = function(events, duplicateStrategy) { // still need to set the right metadata on this event setEventMetadata( events[i], - timeline.getState(false), + timeline.getState(EventTimeline.FORWARDS), false ); diff --git a/spec/integ/matrix-client-event-timeline.spec.js b/spec/integ/matrix-client-event-timeline.spec.js index 071213574..f80f08200 100644 --- a/spec/integ/matrix-client-event-timeline.spec.js +++ b/spec/integ/matrix-client-event-timeline.spec.js @@ -3,6 +3,7 @@ var q = require("q"); var sdk = require("../.."); var HttpBackend = require("../mock-request"); var utils = require("../test-utils"); +var EventTimeline = sdk.EventTimeline; var baseUrl = "http://localhost.or.something"; var userId = "@alice:localhost"; @@ -254,8 +255,10 @@ describe("MatrixClient event timelines", function() { expect(tl.getEvents()[i].event).toEqual(EVENTS[i]); expect(tl.getEvents()[i].sender.name).toEqual(userName); } - expect(tl.getPaginationToken(true)).toEqual("start_token"); - expect(tl.getPaginationToken(false)).toEqual("end_token"); + expect(tl.getPaginationToken(EventTimeline.BACKWARDS)) + .toEqual("start_token"); + expect(tl.getPaginationToken(EventTimeline.FORWARDS)) + .toEqual("end_token"); }).catch(exceptFail).done(done); httpBackend.flush().catch(exceptFail); @@ -285,8 +288,8 @@ describe("MatrixClient event timelines", function() { expect(tl.getEvents().length).toEqual(2); expect(tl.getEvents()[1].event).toEqual(EVENTS[0]); expect(tl.getEvents()[1].sender.name).toEqual(userName); - expect(tl.getPaginationToken(true)).toEqual("f_1_1"); - // expect(tl.getPaginationToken(false)).toEqual("s_5_4"); + expect(tl.getPaginationToken(EventTimeline.BACKWARDS)).toEqual("f_1_1"); + // expect(tl.getPaginationToken(EventTimeline.FORWARDS)).toEqual("s_5_4"); }).catch(exceptFail).done(done); httpBackend.flush().catch(exceptFail); @@ -330,8 +333,10 @@ describe("MatrixClient event timelines", function() { expect(tl.getEvents()[0].event).toEqual(EVENTS[1]); expect(tl.getEvents()[1].event).toEqual(EVENTS[2]); expect(tl.getEvents()[3].event).toEqual(EVENTS[3]); - expect(tl.getPaginationToken(true)).toEqual("start_token"); - // expect(tl.getPaginationToken(false)).toEqual("s_5_4"); + expect(tl.getPaginationToken(EventTimeline.BACKWARDS)) + .toEqual("start_token"); + // expect(tl.getPaginationToken(EventTimeline.FORWARDS)) + // .toEqual("s_5_4"); }).catch(exceptFail).done(done); }); @@ -415,12 +420,18 @@ describe("MatrixClient event timelines", function() { expect(tl.getEvents().length).toEqual(2); expect(tl.getEvents()[0].event).toEqual(EVENTS[1]); expect(tl.getEvents()[1].event).toEqual(EVENTS[2]); - expect(tl.getNeighbouringTimeline(true)).toBe(tl0); - expect(tl.getNeighbouringTimeline(false)).toBe(tl3); - expect(tl0.getPaginationToken(true)).toEqual("start_token0"); - expect(tl0.getPaginationToken(false)).toBe(null); - expect(tl3.getPaginationToken(true)).toBe(null); - expect(tl3.getPaginationToken(false)).toEqual("end_token3"); + expect(tl.getNeighbouringTimeline(EventTimeline.BACKWARDS)) + .toBe(tl0); + expect(tl.getNeighbouringTimeline(EventTimeline.FORWARDS)) + .toBe(tl3); + expect(tl0.getPaginationToken(EventTimeline.BACKWARDS)) + .toEqual("start_token0"); + expect(tl0.getPaginationToken(EventTimeline.FORWARDS)) + .toBe(null); + expect(tl3.getPaginationToken(EventTimeline.BACKWARDS)) + .toBe(null); + expect(tl3.getPaginationToken(EventTimeline.FORWARDS)) + .toEqual("end_token3"); }).catch(exceptFail).done(done); httpBackend.flush().catch(exceptFail); @@ -494,8 +505,10 @@ describe("MatrixClient event timelines", function() { expect(tl.getEvents()[0].event).toEqual(EVENTS[2]); expect(tl.getEvents()[1].event).toEqual(EVENTS[1]); expect(tl.getEvents()[2].event).toEqual(EVENTS[0]); - expect(tl.getPaginationToken(true)).toEqual("start_token1"); - expect(tl.getPaginationToken(false)).toEqual("end_token0"); + expect(tl.getPaginationToken(EventTimeline.BACKWARDS)) + .toEqual("start_token1"); + expect(tl.getPaginationToken(EventTimeline.FORWARDS)) + .toEqual("end_token0"); }).catch(exceptFail).done(done); httpBackend.flush().catch(exceptFail); @@ -535,15 +548,18 @@ describe("MatrixClient event timelines", function() { client.getEventTimeline(room, EVENTS[0].event_id ).then(function(tl0) { tl = tl0; - return client.paginateEventTimeline(tl, {backwards: false, limit: 20}); + return client.paginateEventTimeline( + tl, {backwards: false, limit: 20}); }).then(function(success) { expect(success).toBeTruthy(); expect(tl.getEvents().length).toEqual(3); expect(tl.getEvents()[0].event).toEqual(EVENTS[0]); expect(tl.getEvents()[1].event).toEqual(EVENTS[1]); expect(tl.getEvents()[2].event).toEqual(EVENTS[2]); - expect(tl.getPaginationToken(true)).toEqual("start_token0"); - expect(tl.getPaginationToken(false)).toEqual("end_token1"); + expect(tl.getPaginationToken(EventTimeline.BACKWARDS)) + .toEqual("start_token0"); + expect(tl.getPaginationToken(EventTimeline.FORWARDS)) + .toEqual("end_token1"); }).catch(exceptFail).done(done); httpBackend.flush().catch(exceptFail); diff --git a/spec/unit/event-timeline.spec.js b/spec/unit/event-timeline.spec.js index 010452eda..7a0a9f478 100644 --- a/spec/unit/event-timeline.spec.js +++ b/spec/unit/event-timeline.spec.js @@ -74,48 +74,54 @@ describe("EventTimeline", function() { describe("paginationTokens", function() { it("pagination tokens should start null", function() { - expect(timeline.getPaginationToken(true)).toBe(null); - expect(timeline.getPaginationToken(false)).toBe(null); + expect(timeline.getPaginationToken(EventTimeline.BACKWARDS)).toBe(null); + expect(timeline.getPaginationToken(EventTimeline.FORWARDS)).toBe(null); }); - it("setPaginationToken should set token", function() { - timeline.setPaginationToken("back", true); - timeline.setPaginationToken("fwd", false); - expect(timeline.getPaginationToken(true)).toEqual("back"); - expect(timeline.getPaginationToken(false)).toEqual("fwd"); + it("setPaginationToken should set token", function() { + timeline.setPaginationToken("back", EventTimeline.BACKWARDS); + timeline.setPaginationToken("fwd", EventTimeline.FORWARDS); + expect(timeline.getPaginationToken(EventTimeline.BACKWARDS)).toEqual("back"); + expect(timeline.getPaginationToken(EventTimeline.FORWARDS)).toEqual("fwd"); }); }); describe("neighbouringTimelines", function() { it("neighbouring timelines should start null", function() { - expect(timeline.getNeighbouringTimeline(true)).toBe(null); - expect(timeline.getNeighbouringTimeline(false)).toBe(null); + expect(timeline.getNeighbouringTimeline(EventTimeline.BACKWARDS)).toBe(null); + expect(timeline.getNeighbouringTimeline(EventTimeline.FORWARDS)).toBe(null); }); it("setNeighbouringTimeline should set neighbour", function() { var prev = {a: "a"}; var next = {b: "b"}; - timeline.setNeighbouringTimeline(prev, true); - timeline.setNeighbouringTimeline(next, false); - expect(timeline.getNeighbouringTimeline(true)).toBe(prev); - expect(timeline.getNeighbouringTimeline(false)).toBe(next); + timeline.setNeighbouringTimeline(prev, EventTimeline.BACKWARDS); + timeline.setNeighbouringTimeline(next, EventTimeline.FORWARDS); + expect(timeline.getNeighbouringTimeline(EventTimeline.BACKWARDS)).toBe(prev); + expect(timeline.getNeighbouringTimeline(EventTimeline.FORWARDS)).toBe(next); }); it("setNeighbouringTimeline should throw if called twice", function() { var prev = {a: "a"}; var next = {b: "b"}; - expect(function() {timeline.setNeighbouringTimeline(prev, true);}). - not.toThrow(); - expect(timeline.getNeighbouringTimeline(true)).toBe(prev); - expect(function() {timeline.setNeighbouringTimeline(prev, true);}). - toThrow(); + expect(function() { + timeline.setNeighbouringTimeline(prev, EventTimeline.BACKWARDS); + }).not.toThrow(); + expect(timeline.getNeighbouringTimeline(EventTimeline.BACKWARDS)) + .toBe(prev); + expect(function() { + timeline.setNeighbouringTimeline(prev, EventTimeline.BACKWARDS); + }).toThrow(); - expect(function() {timeline.setNeighbouringTimeline(next, false);}). - not.toThrow(); - expect(timeline.getNeighbouringTimeline(false)).toBe(next); - expect(function() {timeline.setNeighbouringTimeline(next, false);}). - toThrow(); + expect(function() { + timeline.setNeighbouringTimeline(next, EventTimeline.FORWARDS); + }).not.toThrow(); + expect(timeline.getNeighbouringTimeline(EventTimeline.FORWARDS)) + .toBe(next); + expect(function() { + timeline.setNeighbouringTimeline(next, EventTimeline.FORWARDS); + }).toThrow(); }); }); @@ -166,18 +172,20 @@ describe("EventTimeline", function() { membership: "join", name: "Old Alice" }; - timeline.getState(false).getSentinelMember.andCallFake(function(uid) { - if (uid === userA) { - return sentinel; - } - return null; - }); - timeline.getState(true).getSentinelMember.andCallFake(function(uid) { - if (uid === userA) { - return oldSentinel; - } - return null; - }); + timeline.getState(EventTimeline.FORWARDS).getSentinelMember + .andCallFake(function(uid) { + if (uid === userA) { + return sentinel; + } + return null; + }); + timeline.getState(EventTimeline.BACKWARDS).getSentinelMember + .andCallFake(function(uid) { + if (uid === userA) { + return oldSentinel; + } + return null; + }); var newEv = utils.mkEvent({ type: "m.room.name", room: roomId, user: userA, event: true, @@ -206,18 +214,20 @@ describe("EventTimeline", function() { membership: "join", name: "Old Alice" }; - timeline.getState(false).getSentinelMember.andCallFake(function(uid) { - if (uid === userA) { - return sentinel; - } - return null; - }); - timeline.getState(true).getSentinelMember.andCallFake(function(uid) { - if (uid === userA) { - return oldSentinel; - } - return null; - }); + timeline.getState(EventTimeline.FORWARDS).getSentinelMember + .andCallFake(function(uid) { + if (uid === userA) { + return sentinel; + } + return null; + }); + timeline.getState(EventTimeline.BACKWARDS).getSentinelMember + .andCallFake(function(uid) { + if (uid === userA) { + return oldSentinel; + } + return null; + }); var newEv = utils.mkMembership({ room: roomId, mship: "invite", user: userB, skey: userA, event: true @@ -248,15 +258,15 @@ describe("EventTimeline", function() { timeline.addEvent(events[0], false); timeline.addEvent(events[1], false); - expect(timeline.getState(false).setStateEvents). + expect(timeline.getState(EventTimeline.FORWARDS).setStateEvents). toHaveBeenCalledWith([events[0]]); - expect(timeline.getState(false).setStateEvents). + expect(timeline.getState(EventTimeline.FORWARDS).setStateEvents). toHaveBeenCalledWith([events[1]]); expect(events[0].forwardLooking).toBe(true); expect(events[1].forwardLooking).toBe(true); - expect(timeline.getState(true).setStateEvents). + expect(timeline.getState(EventTimeline.BACKWARDS).setStateEvents). not.toHaveBeenCalled(); }); @@ -278,15 +288,15 @@ describe("EventTimeline", function() { timeline.addEvent(events[0], true); timeline.addEvent(events[1], true); - expect(timeline.getState(true).setStateEvents). + expect(timeline.getState(EventTimeline.BACKWARDS).setStateEvents). toHaveBeenCalledWith([events[0]]); - expect(timeline.getState(true).setStateEvents). + expect(timeline.getState(EventTimeline.BACKWARDS).setStateEvents). toHaveBeenCalledWith([events[1]]); expect(events[0].forwardLooking).toBe(false); expect(events[1].forwardLooking).toBe(false); - expect(timeline.getState(false).setStateEvents). + expect(timeline.getState(EventTimeline.FORWARDS).setStateEvents). not.toHaveBeenCalled(); }); });