From d30c46a641a061c6f54694ef642d469dcf175f98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 14 Jan 2020 11:20:56 +0100 Subject: [PATCH 01/23] FilePanel: Refactor out the file panel and convert the methods to async ones. --- src/components/structures/FilePanel.js | 62 +++++++++++++++----------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 61b3d2d4b9..6728472a6c 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -40,42 +40,50 @@ const FilePanel = createReactClass({ }; }, - componentDidMount: function() { - this.updateTimelineSet(this.props.roomId); + async componentDidMount() { + await this.updateTimelineSet(this.props.roomId); }, - updateTimelineSet: function(roomId) { + async fetchFileEventsServer(room) { + const client = MatrixClientPeg.get(); + + const filter = new Matrix.Filter(client.credentials.userId); + filter.setDefinition( + { + "room": { + "timeline": { + "contains_url": true, + "types": [ + "m.room.message", + ], + }, + }, + }, + ); + + // FIXME: we shouldn't be doing this every time we change room - see comment above. + // TODO: Remove this stale comment? Which comment above? + const filterId = await client.getOrCreateFilter("FILTER_FILES_" + client.credentials.userId, filter) + filter.filterId = filterId; + const timelineSet = room.getOrCreateFilteredTimelineSet(filter); + + return timelineSet; + }, + + async updateTimelineSet(roomId: string) { const client = MatrixClientPeg.get(); const room = client.getRoom(roomId); this.noRoom = !room; if (room) { - const filter = new Matrix.Filter(client.credentials.userId); - filter.setDefinition( - { - "room": { - "timeline": { - "contains_url": true, - "types": [ - "m.room.message", - ], - }, - }, - }, - ); + try { + let timelineSet = await this.fetchFileEventsServer(room) + this.setState({ timelineSet: timelineSet }); - // FIXME: we shouldn't be doing this every time we change room - see comment above. - client.getOrCreateFilter("FILTER_FILES_" + client.credentials.userId, filter).then( - (filterId)=>{ - filter.filterId = filterId; - const timelineSet = room.getOrCreateFilteredTimelineSet(filter); - this.setState({ timelineSet: timelineSet }); - }, - (error)=>{ - console.error("Failed to get or create file panel filter", error); - }, - ); + } catch (error) { + console.error("Failed to get or create file panel filter", error); + } } else { console.error("Failed to add filtered timelineSet for FilePanel as no room!"); } From 4f63b10465c28db5f9abb6e5d04fd241b03e74f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 15 Jan 2020 10:41:33 +0100 Subject: [PATCH 02/23] EventIndex: Live events can be unencrypted as well. --- src/indexing/EventIndex.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index c912e31fa5..0980413eb5 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -170,7 +170,12 @@ export default class EventIndex { return; } - const e = ev.toJSON().decrypted; + const jsonEvent = ev.toJSON(); + + let e; + if (ev.isEncrypted()) e = jsonEvent.decrypted; + else e = jsonEvent; + const profile = { displayname: ev.sender.rawDisplayName, avatar_url: ev.sender.getMxcAvatarUrl(), From 263370c9ae6d13038157417e71220a9c5e5cfd87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 15 Jan 2020 12:04:27 +0100 Subject: [PATCH 03/23] BaseEventIndexManager: Add a method to load file events of a room. --- src/indexing/BaseEventIndexManager.js | 30 ++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/indexing/BaseEventIndexManager.js b/src/indexing/BaseEventIndexManager.js index 5e8ca668ad..d7b322bb1a 100644 --- a/src/indexing/BaseEventIndexManager.js +++ b/src/indexing/BaseEventIndexManager.js @@ -62,11 +62,18 @@ export interface SearchArgs { room_id: ?string; } -export interface HistoricEvent { +export interface EventAndProfile { event: MatrixEvent; profile: MatrixProfile; } +export interface LoadArgs { + roomId: string; + limit: number; + fromEvent: string; + direction: string; +} + /** * Base class for classes that provide platform-specific event indexing. * @@ -145,7 +152,7 @@ export default class BaseEventIndexManager { * * This is used to add a batch of events to the index. * - * @param {[HistoricEvent]} events The list of events and profiles that + * @param {[EventAndProfile]} events The list of events and profiles that * should be added to the event index. * @param {[CrawlerCheckpoint]} checkpoint A new crawler checkpoint that * should be stored in the index which should be used to continue crawling @@ -158,7 +165,7 @@ export default class BaseEventIndexManager { * were already added to the index, false otherwise. */ async addHistoricEvents( - events: [HistoricEvent], + events: [EventAndProfile], checkpoint: CrawlerCheckpoint | null, oldCheckpoint: CrawlerCheckpoint | null, ): Promise { @@ -201,6 +208,23 @@ export default class BaseEventIndexManager { throw new Error("Unimplemented"); } + /** Load events that contain an mxc URL to a file from the index. + * + * @param {object} args Arguments object for the method. + * @param {string} args.roomId The ID of the room for which the events + * should be loaded. + * @param {number} args.limit The maximum number of events to return. + * @param {string} args.fromEvent An event id of a previous event returned + * by this method. If set events that are older than the event with the + * given event ID will be returned. + * + * @return {Promise<[EventAndProfile]>} A promise that will resolve to an array + * of Matrix events that contain mxc URLs. + */ + async loadFileEvents(args: LoadArgs): Promise<[EventAndProfile]> { + throw new Error("Unimplemented"); + } + /** * close our event index. * From 8a17c73b79d13eddd31418de59716870a5b2cfeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 15 Jan 2020 12:05:02 +0100 Subject: [PATCH 04/23] EventIndex: Add a method to populate an event timeline with file events. --- src/indexing/EventIndex.js | 68 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index 0980413eb5..501e21b29d 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -17,6 +17,12 @@ limitations under the License. import PlatformPeg from "../PlatformPeg"; import {MatrixClientPeg} from "../MatrixClientPeg"; +import Matrix from 'matrix-js-sdk'; +import EventTimelineSet from 'matrix-js-sdk/lib/models/event-timeline-set'; +import EventTimeline from 'matrix-js-sdk/lib/models/event-timeline'; +import MatrixEvent from 'matrix-js-sdk/lib/models/event'; +import RoomMember from 'matrix-js-sdk/lib/models/room-member'; + /* * Event indexing class that wraps the platform specific event indexing. */ @@ -411,4 +417,66 @@ export default class EventIndex { const indexManager = PlatformPeg.get().getEventIndexingManager(); return indexManager.searchEventIndex(searchArgs); } + + async populateFileTimeline(room, timelineSet) { + const client = MatrixClientPeg.get(); + const indexManager = PlatformPeg.get().getEventIndexingManager(); + + // Get our events from the event index. + const events = await indexManager.loadFileEvents( + { + roomId: room.roomId, + limit: 10 + } + ); + + let eventMapper = client.getEventMapper(); + + // Turn the events into MatrixEvent objects. + const matrixEvents = events.map(e => { + const matrixEvent = eventMapper(e.event); + + const member = new RoomMember(room.roomId, matrixEvent.getSender()); + + // We can't really reconstruct the whole room state from our + // EventIndex to calculate the correct display name. Use the + // disambiguated form always instead. + member.name = e.profile.displayname + " (" + matrixEvent.getSender() + ")"; + + // This is sets the avatar URL. + const memberEvent = eventMapper( + { + content: { + membership: "join", + avatar_url: e.profile.avatar_url, + displayname: e.profile.displayname, + }, + type: "m.room.member", + event_id: matrixEvent.getId() + ":eventIndex", + room_id: matrixEvent.getRoomId(), + sender: matrixEvent.getSender(), + origin_server_ts: matrixEvent.getTs(), + state_key: matrixEvent.getSender() + } + ); + + // We set this manually to avoid emitting RoomMember.membership and + // RoomMember.name events. + member.events.member = memberEvent; + matrixEvent.sender = member; + + return matrixEvent; + }); + + // Add the events to the live timeline of the file panel. + matrixEvents.forEach(e => { + if (!timelineSet.eventIdToTimeline(e.getId())) { + const liveTimeline = timelineSet.getLiveTimeline(); + timelineSet.addEventToTimeline(e, liveTimeline, true) + } + }); + + // Set the pagination token to the oldest event that we retrieved. + timelineSet.getLiveTimeline().setPaginationToken("", EventTimeline.BACKWARDS); + } } From a1cbff3c8cefa3945c5ebedc85e31dd305d2b9ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 15 Jan 2020 12:05:43 +0100 Subject: [PATCH 05/23] FilePanel: Use the event index in encrypted rooms to populate the panel. --- src/components/structures/FilePanel.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 6728472a6c..56bc9dee64 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -22,6 +22,7 @@ import PropTypes from 'prop-types'; import Matrix from 'matrix-js-sdk'; import * as sdk from '../../index'; import {MatrixClientPeg} from '../../MatrixClientPeg'; +import {EventIndexPeg} from "../../indexing/EventIndexPeg"; import { _t } from '../../languageHandler'; /* @@ -73,12 +74,20 @@ const FilePanel = createReactClass({ async updateTimelineSet(roomId: string) { const client = MatrixClientPeg.get(); const room = client.getRoom(roomId); + const eventIndex = EventIndexPeg.get(); this.noRoom = !room; if (room) { + let timelineSet; + try { - let timelineSet = await this.fetchFileEventsServer(room) + timelineSet = await this.fetchFileEventsServer(room) + + if (client.isRoomEncrypted(roomId) && eventIndex !== null) { + await eventIndex.populateFileTimeline(room, timelineSet); + } + this.setState({ timelineSet: timelineSet }); } catch (error) { From 7fb3645e940073a7c7db4e022e1655ca7facde7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 15 Jan 2020 12:06:29 +0100 Subject: [PATCH 06/23] LifeCycle: Start the event index before the client. --- src/Lifecycle.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Lifecycle.js b/src/Lifecycle.js index 0796e326a0..aab7884b2e 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -588,8 +588,8 @@ async function startMatrixClient(startSyncing=true) { Mjolnir.sharedInstance().start(); if (startSyncing) { - await MatrixClientPeg.start(); await EventIndexPeg.init(); + await MatrixClientPeg.start(); } else { console.warn("Caller requested only auxiliary services be started"); await MatrixClientPeg.assign(); From 49c1dbe42133dd8103e1d0d8036d5bcc29c32c06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 17 Jan 2020 10:04:53 +0100 Subject: [PATCH 07/23] FilePanel: Implement pagination requesting using the EventIndex. --- src/components/structures/FilePanel.js | 18 +++- src/components/structures/TimelinePanel.js | 14 ++- src/indexing/EventIndex.js | 99 +++++++++++++++++++--- 3 files changed, 119 insertions(+), 12 deletions(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 56bc9dee64..74b434cdbf 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -71,6 +71,20 @@ const FilePanel = createReactClass({ return timelineSet; }, + onPaginationRequest(timelineWindow, direction, limit) { + const client = MatrixClientPeg.get(); + const eventIndex = EventIndexPeg.get(); + const roomId = this.props.roomId; + + const room = client.getRoom(roomId); + + if (client.isRoomEncrypted(roomId) && eventIndex !== null) { + return eventIndex.paginateTimelineWindow(room, timelineWindow, direction, limit); + } else { + return timelineWindow.paginate(direction, limit); + } + }, + async updateTimelineSet(roomId: string) { const client = MatrixClientPeg.get(); const room = client.getRoom(roomId); @@ -85,7 +99,8 @@ const FilePanel = createReactClass({ timelineSet = await this.fetchFileEventsServer(room) if (client.isRoomEncrypted(roomId) && eventIndex !== null) { - await eventIndex.populateFileTimeline(room, timelineSet); + const timeline = timelineSet.getLiveTimeline(); + await eventIndex.populateFileTimeline(timelineSet, timeline, room, 1); } this.setState({ timelineSet: timelineSet }); @@ -128,6 +143,7 @@ const FilePanel = createReactClass({ manageReadMarkers={false} timelineSet={this.state.timelineSet} showUrlPreview = {false} + onPaginationRequest={this.onPaginationRequest} tileShape="file_grid" resizeNotifier={this.props.resizeNotifier} empty={_t('There are no visible files in this room')} diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index 30b02bfcca..41b1b6f675 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -94,6 +94,10 @@ const TimelinePanel = createReactClass({ // callback which is called when the read-up-to mark is updated. onReadMarkerUpdated: PropTypes.func, + // callback which is called when we wish to paginate the timeline + // window. + onPaginationRequest: PropTypes.func, + // maximum number of events to show in a timeline timelineCap: PropTypes.number, @@ -338,6 +342,14 @@ const TimelinePanel = createReactClass({ } }, + onPaginationRequest(timelineWindow, direction, size) { + if (this.props.onPaginationRequest) { + return this.props.onPaginationRequest(timelineWindow, direction, size); + } else { + return timelineWindow.paginate(direction, size); + } + }, + // set off a pagination request. onMessageListFillRequest: function(backwards) { if (!this._shouldPaginate()) return Promise.resolve(false); @@ -360,7 +372,7 @@ const TimelinePanel = createReactClass({ debuglog("TimelinePanel: Initiating paginate; backwards:"+backwards); this.setState({[paginatingKey]: true}); - return this._timelineWindow.paginate(dir, PAGINATE_SIZE).then((r) => { + return this.onPaginationRequest(this._timelineWindow, dir, PAGINATE_SIZE).then((r) => { if (this.unmounted) { return; } debuglog("TimelinePanel: paginate complete backwards:"+backwards+"; success:"+r); diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index 501e21b29d..7263d8b2c4 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -418,17 +418,29 @@ export default class EventIndex { return indexManager.searchEventIndex(searchArgs); } - async populateFileTimeline(room, timelineSet) { + async loadFileEvents(room, limit = 10, fromEvent = null, direction = EventTimeline.BACKWARDS) { const client = MatrixClientPeg.get(); const indexManager = PlatformPeg.get().getEventIndexingManager(); + let loadArgs = { + roomId: room.roomId, + limit: limit + } + + if (fromEvent) { + loadArgs.fromEvent = fromEvent; + loadArgs.direction = direction; + } + + let events + // Get our events from the event index. - const events = await indexManager.loadFileEvents( - { - roomId: room.roomId, - limit: 10 - } - ); + try { + events = await indexManager.loadFileEvents(loadArgs); + } catch (e) { + console.log("EventIndex: Error getting file events", e); + return [] + } let eventMapper = client.getEventMapper(); @@ -468,15 +480,82 @@ export default class EventIndex { return matrixEvent; }); + return matrixEvents; + } + + async populateFileTimeline(timelineSet, timeline, room, limit = 10, + fromEvent = null, direction = EventTimeline.BACKWARDS) { + let matrixEvents = await this.loadFileEvents(room, limit, fromEvent, direction); + // Add the events to the live timeline of the file panel. matrixEvents.forEach(e => { if (!timelineSet.eventIdToTimeline(e.getId())) { - const liveTimeline = timelineSet.getLiveTimeline(); - timelineSet.addEventToTimeline(e, liveTimeline, true) + timelineSet.addEventToTimeline(e, timeline, + direction == EventTimeline.BACKWARDS) } }); // Set the pagination token to the oldest event that we retrieved. - timelineSet.getLiveTimeline().setPaginationToken("", EventTimeline.BACKWARDS); + if (matrixEvents.length > 0) { + timeline.setPaginationToken(matrixEvents[matrixEvents.length - 1].getId(), + EventTimeline.BACKWARDS); + return true; + } else { + timeline.setPaginationToken("", EventTimeline.BACKWARDS); + return false; + } + } + + paginateTimelineWindow(room, timelineWindow, direction, limit) { + let tl; + + // TODO this is from the js-sdk, this should probably be exposed to + // us through the js-sdk. + const moveWindowCap = (titmelineWindow, timeline, direction, limit) => { + var count = (direction == EventTimeline.BACKWARDS) ? + timeline.retreat(limit) : timeline.advance(limit); + + if (count) { + timelineWindow._eventCount += count; + var excess = timelineWindow._eventCount - timelineWindow._windowLimit; + + if (excess > 0) { + timelineWindow.unpaginate(3, direction != EventTimeline.BACKWARDS); + } + return true; + } + + return false; + }; + + // TODO these private fields should be somehow exposed in the js-sdk. + if (direction == EventTimeline.BACKWARDS) tl = timelineWindow._start; + else if (direction == EventTimeline.FORWARDS) tl = timelineWindow._end; + + if (!tl) return Promise.resolve(false); + if (tl.pendingPaginate) return tl.pendingPaginate; + + if (moveWindowCap(timelineWindow, tl, direction, limit)) { + return Promise.resolve(true); + } + + const paginationMethod = async (timelineWindow, timeline, room, direction, limit) => { + const timelineSet = timelineWindow._timelineSet; + const token = timeline.timeline.getPaginationToken(direction); + + const ret = await this.populateFileTimeline(timelineSet, timeline.timeline, + room, limit, token, direction); + + moveWindowCap(timelineWindow, timeline, direction, limit) + timeline.pendingPaginate = null; + + return ret; + }; + + const paginationPromise = paginationMethod(timelineWindow, tl, room, + direction, limit); + tl.pendingPaginate = paginationPromise; + + return paginationPromise; } } From 70d394e668615d431a0c9f9ed4f2c16cad1c2a34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 17 Jan 2020 10:35:33 +0100 Subject: [PATCH 08/23] EventIndex: Update the imports for the new build system. --- src/components/structures/FilePanel.js | 2 +- src/indexing/EventIndex.js | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 74b434cdbf..0ef9331338 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -22,7 +22,7 @@ import PropTypes from 'prop-types'; import Matrix from 'matrix-js-sdk'; import * as sdk from '../../index'; import {MatrixClientPeg} from '../../MatrixClientPeg'; -import {EventIndexPeg} from "../../indexing/EventIndexPeg"; +import EventIndexPeg from "../../indexing/EventIndexPeg"; import { _t } from '../../languageHandler'; /* diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index 7263d8b2c4..cb2f646d07 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -17,11 +17,9 @@ limitations under the License. import PlatformPeg from "../PlatformPeg"; import {MatrixClientPeg} from "../MatrixClientPeg"; -import Matrix from 'matrix-js-sdk'; -import EventTimelineSet from 'matrix-js-sdk/lib/models/event-timeline-set'; -import EventTimeline from 'matrix-js-sdk/lib/models/event-timeline'; -import MatrixEvent from 'matrix-js-sdk/lib/models/event'; -import RoomMember from 'matrix-js-sdk/lib/models/room-member'; +import * as Matrix from 'matrix-js-sdk'; +import {EventTimelineSet} from 'matrix-js-sdk'; +import {EventTimeline} from 'matrix-js-sdk'; /* * Event indexing class that wraps the platform specific event indexing. @@ -448,7 +446,7 @@ export default class EventIndex { const matrixEvents = events.map(e => { const matrixEvent = eventMapper(e.event); - const member = new RoomMember(room.roomId, matrixEvent.getSender()); + const member = new Matrix.RoomMember(room.roomId, matrixEvent.getSender()); // We can't really reconstruct the whole room state from our // EventIndex to calculate the correct display name. Use the From 95b86b42d00b1b23cfaed2f1aa474b962d55fe2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 17 Jan 2020 11:06:05 +0100 Subject: [PATCH 09/23] BaseEventIndexManager: Update the docs for the loadFileEvents method. --- src/indexing/BaseEventIndexManager.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/indexing/BaseEventIndexManager.js b/src/indexing/BaseEventIndexManager.js index d7b322bb1a..c4758bcaa3 100644 --- a/src/indexing/BaseEventIndexManager.js +++ b/src/indexing/BaseEventIndexManager.js @@ -215,11 +215,14 @@ export default class BaseEventIndexManager { * should be loaded. * @param {number} args.limit The maximum number of events to return. * @param {string} args.fromEvent An event id of a previous event returned - * by this method. If set events that are older than the event with the - * given event ID will be returned. + * by this method. Passing this means that we are going to continue loading + * events from this point in the history. + * @param {string} args.direction The direction to which we should continue + * loading events from. This is used only if fromEvent is used as well. * - * @return {Promise<[EventAndProfile]>} A promise that will resolve to an array - * of Matrix events that contain mxc URLs. + * @return {Promise<[EventAndProfile]>} A promise that will resolve to an + * array of Matrix events that contain mxc URLs accompanied with the + * historic profile of the sender. */ async loadFileEvents(args: LoadArgs): Promise<[EventAndProfile]> { throw new Error("Unimplemented"); From ccfe3c7e70e9b9d5e8beddf7e8dc4c3e111bab56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 17 Jan 2020 11:52:20 +0100 Subject: [PATCH 10/23] FilePanel/EventIndex: Fix lint errors. --- src/components/structures/FilePanel.js | 6 +++--- src/indexing/EventIndex.js | 27 +++++++++++++------------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 0ef9331338..6faec27284 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -64,7 +64,8 @@ const FilePanel = createReactClass({ // FIXME: we shouldn't be doing this every time we change room - see comment above. // TODO: Remove this stale comment? Which comment above? - const filterId = await client.getOrCreateFilter("FILTER_FILES_" + client.credentials.userId, filter) + const filterId = await client.getOrCreateFilter("FILTER_FILES_" + client.credentials.userId, + filter); filter.filterId = filterId; const timelineSet = room.getOrCreateFilteredTimelineSet(filter); @@ -96,7 +97,7 @@ const FilePanel = createReactClass({ let timelineSet; try { - timelineSet = await this.fetchFileEventsServer(room) + timelineSet = await this.fetchFileEventsServer(room); if (client.isRoomEncrypted(roomId) && eventIndex !== null) { const timeline = timelineSet.getLiveTimeline(); @@ -104,7 +105,6 @@ const FilePanel = createReactClass({ } this.setState({ timelineSet: timelineSet }); - } catch (error) { console.error("Failed to get or create file panel filter", error); } diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index cb2f646d07..0e48af749c 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -18,7 +18,6 @@ import PlatformPeg from "../PlatformPeg"; import {MatrixClientPeg} from "../MatrixClientPeg"; import * as Matrix from 'matrix-js-sdk'; -import {EventTimelineSet} from 'matrix-js-sdk'; import {EventTimeline} from 'matrix-js-sdk'; /* @@ -420,27 +419,27 @@ export default class EventIndex { const client = MatrixClientPeg.get(); const indexManager = PlatformPeg.get().getEventIndexingManager(); - let loadArgs = { + const loadArgs = { roomId: room.roomId, - limit: limit - } + limit: limit, + }; if (fromEvent) { loadArgs.fromEvent = fromEvent; loadArgs.direction = direction; } - let events + let events; // Get our events from the event index. try { events = await indexManager.loadFileEvents(loadArgs); } catch (e) { console.log("EventIndex: Error getting file events", e); - return [] + return []; } - let eventMapper = client.getEventMapper(); + const eventMapper = client.getEventMapper(); // Turn the events into MatrixEvent objects. const matrixEvents = events.map(e => { @@ -466,8 +465,8 @@ export default class EventIndex { room_id: matrixEvent.getRoomId(), sender: matrixEvent.getSender(), origin_server_ts: matrixEvent.getTs(), - state_key: matrixEvent.getSender() - } + state_key: matrixEvent.getSender(), + }, ); // We set this manually to avoid emitting RoomMember.membership and @@ -483,13 +482,13 @@ export default class EventIndex { async populateFileTimeline(timelineSet, timeline, room, limit = 10, fromEvent = null, direction = EventTimeline.BACKWARDS) { - let matrixEvents = await this.loadFileEvents(room, limit, fromEvent, direction); + const matrixEvents = await this.loadFileEvents(room, limit, fromEvent, direction); // Add the events to the live timeline of the file panel. matrixEvents.forEach(e => { if (!timelineSet.eventIdToTimeline(e.getId())) { timelineSet.addEventToTimeline(e, timeline, - direction == EventTimeline.BACKWARDS) + direction == EventTimeline.BACKWARDS); } }); @@ -510,12 +509,12 @@ export default class EventIndex { // TODO this is from the js-sdk, this should probably be exposed to // us through the js-sdk. const moveWindowCap = (titmelineWindow, timeline, direction, limit) => { - var count = (direction == EventTimeline.BACKWARDS) ? + const count = (direction == EventTimeline.BACKWARDS) ? timeline.retreat(limit) : timeline.advance(limit); if (count) { timelineWindow._eventCount += count; - var excess = timelineWindow._eventCount - timelineWindow._windowLimit; + const excess = timelineWindow._eventCount - timelineWindow._windowLimit; if (excess > 0) { timelineWindow.unpaginate(3, direction != EventTimeline.BACKWARDS); @@ -544,7 +543,7 @@ export default class EventIndex { const ret = await this.populateFileTimeline(timelineSet, timeline.timeline, room, limit, token, direction); - moveWindowCap(timelineWindow, timeline, direction, limit) + moveWindowCap(timelineWindow, timeline, direction, limit); timeline.pendingPaginate = null; return ret; From 9978fee51208b2c6031f611fdbc25f8752d8d7a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Mon, 20 Jan 2020 10:06:20 +0100 Subject: [PATCH 11/23] Lifecycle: Comment why we need to initialize the index before the client. --- src/Lifecycle.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Lifecycle.js b/src/Lifecycle.js index aab7884b2e..b52e5e9da5 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -588,6 +588,9 @@ async function startMatrixClient(startSyncing=true) { Mjolnir.sharedInstance().start(); if (startSyncing) { + // The client might want to populate some views with events from the + // index (e.g. the FilePanel), therefore initialize the event index + // before the client. await EventIndexPeg.init(); await MatrixClientPeg.start(); } else { From 0c854fce9b19543a57e2494b24b494faefcca35a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Mon, 20 Jan 2020 10:09:58 +0100 Subject: [PATCH 12/23] FilePanel: Remove a stale comment. --- src/components/structures/FilePanel.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 6faec27284..cb788cd6d2 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -62,8 +62,6 @@ const FilePanel = createReactClass({ }, ); - // FIXME: we shouldn't be doing this every time we change room - see comment above. - // TODO: Remove this stale comment? Which comment above? const filterId = await client.getOrCreateFilter("FILTER_FILES_" + client.credentials.userId, filter); filter.filterId = filterId; From b4c8a686cec16290f56603d3fe925ddc1d975830 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Mon, 20 Jan 2020 12:41:55 +0100 Subject: [PATCH 13/23] EventIndex: Don't import the whole js-sdk. --- src/indexing/EventIndex.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index 0e48af749c..67a3d4cace 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -16,9 +16,7 @@ limitations under the License. import PlatformPeg from "../PlatformPeg"; import {MatrixClientPeg} from "../MatrixClientPeg"; - -import * as Matrix from 'matrix-js-sdk'; -import {EventTimeline} from 'matrix-js-sdk'; +import {EventTimeline, RoomMember} from 'matrix-js-sdk'; /* * Event indexing class that wraps the platform specific event indexing. @@ -445,7 +443,7 @@ export default class EventIndex { const matrixEvents = events.map(e => { const matrixEvent = eventMapper(e.event); - const member = new Matrix.RoomMember(room.roomId, matrixEvent.getSender()); + const member = new RoomMember(room.roomId, matrixEvent.getSender()); // We can't really reconstruct the whole room state from our // EventIndex to calculate the correct display name. Use the From 0b4b9d8d5d166ed7454b7d729fc5a08478ac6fb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Mon, 20 Jan 2020 12:42:46 +0100 Subject: [PATCH 14/23] EventIndex: Simplify the json event getting logic. --- src/indexing/EventIndex.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index 67a3d4cace..e361f66edc 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -172,10 +172,7 @@ export default class EventIndex { } const jsonEvent = ev.toJSON(); - - let e; - if (ev.isEncrypted()) e = jsonEvent.decrypted; - else e = jsonEvent; + const e = ev.isEncrypted() ? jsonEvent.decrypted: jsonEvent; const profile = { displayname: ev.sender.rawDisplayName, @@ -311,10 +308,7 @@ export default class EventIndex { // consume. const events = filteredEvents.map((ev) => { const jsonEvent = ev.toJSON(); - - let e; - if (ev.isEncrypted()) e = jsonEvent.decrypted; - else e = jsonEvent; + const e = ev.isEncrypted() ? jsonEvent.decrypted: jsonEvent; let profile = {}; if (e.sender in profiles) profile = profiles[e.sender]; From 4cf44cf5a561f801c107b121f109e18236db0e79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Mon, 20 Jan 2020 12:43:20 +0100 Subject: [PATCH 15/23] EventIndex/FilePanel: Allow longer lines. --- src/components/structures/FilePanel.js | 3 +-- src/indexing/EventIndex.js | 16 ++++++---------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index cb788cd6d2..71e8143f0a 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -62,8 +62,7 @@ const FilePanel = createReactClass({ }, ); - const filterId = await client.getOrCreateFilter("FILTER_FILES_" + client.credentials.userId, - filter); + const filterId = await client.getOrCreateFilter("FILTER_FILES_" + client.credentials.userId, filter); filter.filterId = filterId; const timelineSet = room.getOrCreateFilteredTimelineSet(filter); diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index e361f66edc..cb77d92c27 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -479,15 +479,13 @@ export default class EventIndex { // Add the events to the live timeline of the file panel. matrixEvents.forEach(e => { if (!timelineSet.eventIdToTimeline(e.getId())) { - timelineSet.addEventToTimeline(e, timeline, - direction == EventTimeline.BACKWARDS); + timelineSet.addEventToTimeline(e, timeline, direction == EventTimeline.BACKWARDS); } }); // Set the pagination token to the oldest event that we retrieved. if (matrixEvents.length > 0) { - timeline.setPaginationToken(matrixEvents[matrixEvents.length - 1].getId(), - EventTimeline.BACKWARDS); + timeline.setPaginationToken(matrixEvents[matrixEvents.length - 1].getId(), EventTimeline.BACKWARDS); return true; } else { timeline.setPaginationToken("", EventTimeline.BACKWARDS); @@ -501,7 +499,7 @@ export default class EventIndex { // TODO this is from the js-sdk, this should probably be exposed to // us through the js-sdk. const moveWindowCap = (titmelineWindow, timeline, direction, limit) => { - const count = (direction == EventTimeline.BACKWARDS) ? + const count = (direction === EventTimeline.BACKWARDS) ? timeline.retreat(limit) : timeline.advance(limit); if (count) { @@ -509,7 +507,7 @@ export default class EventIndex { const excess = timelineWindow._eventCount - timelineWindow._windowLimit; if (excess > 0) { - timelineWindow.unpaginate(3, direction != EventTimeline.BACKWARDS); + timelineWindow.unpaginate(3, direction !== EventTimeline.BACKWARDS); } return true; } @@ -532,8 +530,7 @@ export default class EventIndex { const timelineSet = timelineWindow._timelineSet; const token = timeline.timeline.getPaginationToken(direction); - const ret = await this.populateFileTimeline(timelineSet, timeline.timeline, - room, limit, token, direction); + const ret = await this.populateFileTimeline(timelineSet, timeline.timeline, room, limit, token, direction); moveWindowCap(timelineWindow, timeline, direction, limit); timeline.pendingPaginate = null; @@ -541,8 +538,7 @@ export default class EventIndex { return ret; }; - const paginationPromise = paginationMethod(timelineWindow, tl, room, - direction, limit); + const paginationPromise = paginationMethod(timelineWindow, tl, room, direction, limit); tl.pendingPaginate = paginationPromise; return paginationPromise; From a0599dedf0b39e66c3ee49f4e772e3380667cd10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 21 Jan 2020 15:45:29 +0100 Subject: [PATCH 16/23] EventIndex: Use the newly exposed TimelineWindow methods. --- src/indexing/EventIndex.js | 29 +++-------------------------- 1 file changed, 3 insertions(+), 26 deletions(-) diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index cb77d92c27..c081440233 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -494,35 +494,12 @@ export default class EventIndex { } paginateTimelineWindow(room, timelineWindow, direction, limit) { - let tl; - - // TODO this is from the js-sdk, this should probably be exposed to - // us through the js-sdk. - const moveWindowCap = (titmelineWindow, timeline, direction, limit) => { - const count = (direction === EventTimeline.BACKWARDS) ? - timeline.retreat(limit) : timeline.advance(limit); - - if (count) { - timelineWindow._eventCount += count; - const excess = timelineWindow._eventCount - timelineWindow._windowLimit; - - if (excess > 0) { - timelineWindow.unpaginate(3, direction !== EventTimeline.BACKWARDS); - } - return true; - } - - return false; - }; - - // TODO these private fields should be somehow exposed in the js-sdk. - if (direction == EventTimeline.BACKWARDS) tl = timelineWindow._start; - else if (direction == EventTimeline.FORWARDS) tl = timelineWindow._end; + const tl = timelineWindow.getTimelineIndex(direction); if (!tl) return Promise.resolve(false); if (tl.pendingPaginate) return tl.pendingPaginate; - if (moveWindowCap(timelineWindow, tl, direction, limit)) { + if (timelineWindow.extend(direction, limit)) { return Promise.resolve(true); } @@ -532,8 +509,8 @@ export default class EventIndex { const ret = await this.populateFileTimeline(timelineSet, timeline.timeline, room, limit, token, direction); - moveWindowCap(timelineWindow, timeline, direction, limit); timeline.pendingPaginate = null; + timelineWindow.extend(direction, limit); return ret; }; From 735ba4fd33d7fcdfb5e6e36141176f20f99395dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 22 Jan 2020 16:11:54 +0100 Subject: [PATCH 17/23] EventIndex: Correctly populate events on initial fill requests. --- src/components/structures/FilePanel.js | 2 +- src/indexing/EventIndex.js | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 71e8143f0a..c96f8770ad 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -98,7 +98,7 @@ const FilePanel = createReactClass({ if (client.isRoomEncrypted(roomId) && eventIndex !== null) { const timeline = timelineSet.getLiveTimeline(); - await eventIndex.populateFileTimeline(timelineSet, timeline, room, 1); + await eventIndex.populateFileTimeline(timelineSet, timeline, room, 10); } this.setState({ timelineSet: timelineSet }); diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index c081440233..93c640cf8e 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -476,6 +476,16 @@ export default class EventIndex { fromEvent = null, direction = EventTimeline.BACKWARDS) { const matrixEvents = await this.loadFileEvents(room, limit, fromEvent, direction); + // If this is a normal fill request, not a pagination request, we need + // to get our events in the BACKWARDS direction but populate them in the + // forwards direction. + // This needs to happen because a fill request might come with an + // exisitng timeline e.g. if you close and re-open the FilePanel. + if (fromEvent === null) { + matrixEvents.reverse(); + direction = direction == EventTimeline.BACKWARDS ? EventTimeline.FORWARDS: EventTimeline.BACKWARDS; + } + // Add the events to the live timeline of the file panel. matrixEvents.forEach(e => { if (!timelineSet.eventIdToTimeline(e.getId())) { From f917c2faea6fabd118bbf08251b9e341c793c740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 22 Jan 2020 16:21:11 +0100 Subject: [PATCH 18/23] FilePanel: Listen for live events and add them to an open FilePanel. --- src/components/structures/FilePanel.js | 57 ++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index c96f8770ad..a25f8babd1 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -30,6 +30,7 @@ import { _t } from '../../languageHandler'; */ const FilePanel = createReactClass({ displayName: 'FilePanel', + decryptingEvents: new Set(), propTypes: { roomId: PropTypes.string.isRequired, @@ -41,8 +42,64 @@ const FilePanel = createReactClass({ }; }, + onRoomTimeline (ev, room, toStartOfTimeline, removed, data) { + if (room.roomId !== this.props.roomId) return; + if (toStartOfTimeline || !data || !data.liveEvent || ev.isRedacted()) return; + + if (ev.isBeingDecrypted()) { + this.decryptingEvents.add(ev.getId()); + } else { + this.addEncryptedLiveEvent(ev); + } + }, + + onEventDecrypted (ev, err) { + if (ev.getRoomId() !== this.props.roomId) return; + const eventId = ev.getId(); + + if (!this.decryptingEvents.delete(eventId)) return; + if (err) return; + + this.addEncryptedLiveEvent(ev); + }, + + addEncryptedLiveEvent(ev, toStartOfTimeline) { + if (!this.state.timelineSet) return; + + const timeline = this.state.timelineSet.getLiveTimeline(); + if (ev.getType() !== "m.room.message") return; + if (["m.file", "m.image", "m.video", "m.audio"].indexOf(ev.getContent().msgtype) == -1) { + return; + } + + if (!this.state.timelineSet.eventIdToTimeline(ev.getId())) { + this.state.timelineSet.addEventToTimeline(ev, timeline, false); + } + }, + async componentDidMount() { + const client = MatrixClientPeg.get(); + await this.updateTimelineSet(this.props.roomId); + + if (!MatrixClientPeg.get().isRoomEncrypted(this.props.roomId)) return; + + if (EventIndexPeg.get() !== null) { + client.on('Room.timeline', this.onRoomTimeline.bind(this)); + client.on('Event.decrypted', this.onEventDecrypted.bind(this)); + } + }, + + componentWillUnmount() { + const client = MatrixClientPeg.get(); + if (client === null) return; + + if (!MatrixClientPeg.get().isRoomEncrypted(this.props.roomId)) return; + + if (EventIndexPeg.get() !== null) { + client.removeListener('Room.timeline', this.onRoomTimeline.bind(this)); + client.removeListener('Event.decrypted', this.onEventDecrypted.bind(this)); + } }, async fetchFileEventsServer(room) { From c5e8753b0535d898961e4e8a0d3d469a86d39fe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 22 Jan 2020 16:26:40 +0100 Subject: [PATCH 19/23] FilePanel: Don't import the whole of the js-sdk. --- src/components/structures/FilePanel.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index a25f8babd1..7ee86436a5 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -19,7 +19,7 @@ import React from 'react'; import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; -import Matrix from 'matrix-js-sdk'; +import {Filter} from 'matrix-js-sdk'; import * as sdk from '../../index'; import {MatrixClientPeg} from '../../MatrixClientPeg'; import EventIndexPeg from "../../indexing/EventIndexPeg"; @@ -105,7 +105,7 @@ const FilePanel = createReactClass({ async fetchFileEventsServer(room) { const client = MatrixClientPeg.get(); - const filter = new Matrix.Filter(client.credentials.userId); + const filter = new Filter(client.credentials.userId); filter.setDefinition( { "room": { From c3418df9197ba46b9275f22e600997ae089f26f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 22 Jan 2020 16:31:49 +0100 Subject: [PATCH 20/23] FilePanel: Remove whitespace before two function definitions. --- src/components/structures/FilePanel.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 7ee86436a5..e03c587e61 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -42,7 +42,7 @@ const FilePanel = createReactClass({ }; }, - onRoomTimeline (ev, room, toStartOfTimeline, removed, data) { + onRoomTimeline(ev, room, toStartOfTimeline, removed, data) { if (room.roomId !== this.props.roomId) return; if (toStartOfTimeline || !data || !data.liveEvent || ev.isRedacted()) return; @@ -53,7 +53,7 @@ const FilePanel = createReactClass({ } }, - onEventDecrypted (ev, err) { + onEventDecrypted(ev, err) { if (ev.getRoomId() !== this.props.roomId) return; const eventId = ev.getId(); From ecfecfe559e2eda9c8cc326913a5103d19fb9ea0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 24 Jan 2020 12:07:03 +0100 Subject: [PATCH 21/23] EventIndex: Fix a small style issue. --- src/indexing/EventIndex.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index 93c640cf8e..2b432ab1a1 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -172,7 +172,7 @@ export default class EventIndex { } const jsonEvent = ev.toJSON(); - const e = ev.isEncrypted() ? jsonEvent.decrypted: jsonEvent; + const e = ev.isEncrypted() ? jsonEvent.decrypted : jsonEvent; const profile = { displayname: ev.sender.rawDisplayName, @@ -308,7 +308,7 @@ export default class EventIndex { // consume. const events = filteredEvents.map((ev) => { const jsonEvent = ev.toJSON(); - const e = ev.isEncrypted() ? jsonEvent.decrypted: jsonEvent; + const e = ev.isEncrypted() ? jsonEvent.decrypted : jsonEvent; let profile = {}; if (e.sender in profiles) profile = profiles[e.sender]; From 3534cd42023882785cc83c6222a1e478302f8131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 24 Jan 2020 14:23:43 +0100 Subject: [PATCH 22/23] FilePanel: Add comments to explain what's going on with the event index. --- src/components/structures/FilePanel.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index e03c587e61..4c02f925fc 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -30,6 +30,8 @@ import { _t } from '../../languageHandler'; */ const FilePanel = createReactClass({ displayName: 'FilePanel', + // This is used to track if a decrypted event was a live event and should be + // added to the timeline. decryptingEvents: new Set(), propTypes: { @@ -84,6 +86,14 @@ const FilePanel = createReactClass({ if (!MatrixClientPeg.get().isRoomEncrypted(this.props.roomId)) return; + // The timelineSets filter makes sure that encrypted events that contain + // URLs never get added to the timeline, even if they are live events. + // These methods are here to manually listen for such events and add + // them despite the filter's best efforts. + // + // We do this only for encrypted rooms and if an event index exists, + // this could be made more general in the future or the filter logic + // could be fixed. if (EventIndexPeg.get() !== null) { client.on('Room.timeline', this.onRoomTimeline.bind(this)); client.on('Event.decrypted', this.onEventDecrypted.bind(this)); @@ -133,6 +143,10 @@ const FilePanel = createReactClass({ const room = client.getRoom(roomId); + // We override the pagination request for encrypted rooms so that we ask + // the event index to fulfill the pagination request. Asking the server + // to paginate won't ever work since the server can't correctly filter + // out events containing URLs if (client.isRoomEncrypted(roomId) && eventIndex !== null) { return eventIndex.paginateTimelineWindow(room, timelineWindow, direction, limit); } else { @@ -153,6 +167,15 @@ const FilePanel = createReactClass({ try { timelineSet = await this.fetchFileEventsServer(room); + // If this room is encrypted the file panel won't be populated + // correctly since the defined filter doesn't support encrypted + // events and the server can't check if encrypted events contain + // URLs. + // + // This is where our event index comes into place, we ask the + // event index to populate the timelineSet for us. This call + // will add 10 events to the live timeline of the set. More can + // be requested using pagination. if (client.isRoomEncrypted(roomId) && eventIndex !== null) { const timeline = timelineSet.getLiveTimeline(); await eventIndex.populateFileTimeline(timelineSet, timeline, room, 10); From 37f289b120ff23752204fd6a98b47b79b648df67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 24 Jan 2020 14:24:25 +0100 Subject: [PATCH 23/23] EventIndex: Add docstrings for the FilePanel methods. --- src/indexing/EventIndex.js | 72 +++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index 2b432ab1a1..b6e29c455d 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -407,6 +407,27 @@ export default class EventIndex { return indexManager.searchEventIndex(searchArgs); } + /** + * Load events that contain URLs from the event index. + * + * @param {Room} room The room for which we should fetch events containing + * URLs + * + * @param {number} limit The maximum number of events to fetch. + * + * @param {string} fromEvent From which event should we continue fetching + * events from the index. This is only needed if we're continuing to fill + * the timeline, e.g. if we're paginating. This needs to be set to a event + * id of an event that was previously fetched with this function. + * + * @param {string} direction The direction in which we will continue + * fetching events. EventTimeline.BACKWARDS to continue fetching events that + * are older than the event given in fromEvent, EventTimeline.FORWARDS to + * fetch newer events. + * + * @returns {Promise} Resolves to an array of events that + * contain URLs. + */ async loadFileEvents(room, limit = 10, fromEvent = null, direction = EventTimeline.BACKWARDS) { const client = MatrixClientPeg.get(); const indexManager = PlatformPeg.get().getEventIndexingManager(); @@ -472,6 +493,33 @@ export default class EventIndex { return matrixEvents; } + /** + * Fill a timeline with events that contain URLs. + * + * @param {TimelineSet} timelineSet The TimelineSet the Timeline belongs to, + * used to check if we're adding duplicate events. + * + * @param {Timeline} timeline The Timeline which should be filed with + * events. + * + * @param {Room} room The room for which we should fetch events containing + * URLs + * + * @param {number} limit The maximum number of events to fetch. + * + * @param {string} fromEvent From which event should we continue fetching + * events from the index. This is only needed if we're continuing to fill + * the timeline, e.g. if we're paginating. This needs to be set to a event + * id of an event that was previously fetched with this function. + * + * @param {string} direction The direction in which we will continue + * fetching events. EventTimeline.BACKWARDS to continue fetching events that + * are older than the event given in fromEvent, EventTimeline.FORWARDS to + * fetch newer events. + * + * @returns {Promise} Resolves to true if events were added to the + * timeline, false otherwise. + */ async populateFileTimeline(timelineSet, timeline, room, limit = 10, fromEvent = null, direction = EventTimeline.BACKWARDS) { const matrixEvents = await this.loadFileEvents(room, limit, fromEvent, direction); @@ -486,7 +534,7 @@ export default class EventIndex { direction = direction == EventTimeline.BACKWARDS ? EventTimeline.FORWARDS: EventTimeline.BACKWARDS; } - // Add the events to the live timeline of the file panel. + // Add the events to the timeline of the file panel. matrixEvents.forEach(e => { if (!timelineSet.eventIdToTimeline(e.getId())) { timelineSet.addEventToTimeline(e, timeline, direction == EventTimeline.BACKWARDS); @@ -503,6 +551,28 @@ export default class EventIndex { } } + /** + * Emulate a TimelineWindow pagination() request with the event index as the event source + * + * Might not fetch events from the index if the timeline already contains + * events that the window isn't showing. + * + * @param {Room} room The room for which we should fetch events containing + * URLs + * + * @param {TimelineWindow} timelineWindow The timeline window that should be + * populated with new events. + * + * @param {string} direction The direction in which we should paginate. + * EventTimeline.BACKWARDS to paginate back, EventTimeline.FORWARDS to + * paginate forwards. + * + * @param {number} limit The maximum number of events to fetch while + * paginating. + * + * @returns {Promise} Resolves to a boolean which is true if more + * events were successfully retrieved. + */ paginateTimelineWindow(room, timelineWindow, direction, limit) { const tl = timelineWindow.getTimelineIndex(direction);